From 258f4d1e8dbe200cc340c713b420b2b92463c1b3 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Sat, 10 Oct 2026 12:43:27 +0900 Subject: [PATCH 1/2] Write the minimum signed integer correctly in CSVWriter to_string for signed integers negated the value before casting it to size_t, which overflows for INT_MIN: the negation wraps back to INT_MIN and the cast sign-extends it, so INT_MIN was written as -18446744071562067968. Negate in unsigned arithmetic instead. --- include/internal/csv_writer.hpp | 4 ++-- tests/test_write_csv.cpp | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/internal/csv_writer.hpp b/include/internal/csv_writer.hpp index 2f02eaf1..8a8506ad 100644 --- a/include/internal/csv_writer.hpp +++ b/include/internal/csv_writer.hpp @@ -128,8 +128,8 @@ namespace internals { csv::enable_if_t::value && std::is_signed::value, int> = 0 > inline std::string to_string(T value) { - return (value >= 0) ? to_string((size_t)value) - : "-" + to_string((size_t)(value * -1)); + return (value >= 0) ? to_string(static_cast(value)) + : "-" + to_string(0ULL - static_cast(value)); } /** to_string() for floating point numbers */ diff --git a/tests/test_write_csv.cpp b/tests/test_write_csv.cpp index c9c8845b..e24a2efd 100644 --- a/tests/test_write_csv.cpp +++ b/tests/test_write_csv.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -55,6 +56,12 @@ TEST_CASE("Numeric Converter Tsts", "[test_convert_number]") { REQUIRE(csv::internals::to_string(-0.666) == "-0.66600"); } + SECTION("Negative Integers") { + REQUIRE(csv::internals::to_string(-1) == "-1"); + REQUIRE(csv::internals::to_string((std::numeric_limits::min)()) == "-2147483648"); + REQUIRE(csv::internals::to_string((std::numeric_limits::min)()) == "-9223372036854775808"); + } + SECTION("Numbers Close to 10^n - Regression") { REQUIRE(csv::internals::to_string(10.0) == "10.0"); REQUIRE(csv::internals::to_string(100.0) == "100.0"); From 267d051c3b5ed2c9a74ddeccd58cee27a3f12759 Mon Sep 17 00:00:00 2001 From: Vincent La Date: Sat, 10 Oct 2026 10:12:19 -0700 Subject: [PATCH 2/2] Expand signed integer writer regressions --- tests/test_write_csv.cpp | 41 ++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/tests/test_write_csv.cpp b/tests/test_write_csv.cpp index e24a2efd..7e06e2a3 100644 --- a/tests/test_write_csv.cpp +++ b/tests/test_write_csv.cpp @@ -56,12 +56,6 @@ TEST_CASE("Numeric Converter Tsts", "[test_convert_number]") { REQUIRE(csv::internals::to_string(-0.666) == "-0.66600"); } - SECTION("Negative Integers") { - REQUIRE(csv::internals::to_string(-1) == "-1"); - REQUIRE(csv::internals::to_string((std::numeric_limits::min)()) == "-2147483648"); - REQUIRE(csv::internals::to_string((std::numeric_limits::min)()) == "-9223372036854775808"); - } - SECTION("Numbers Close to 10^n - Regression") { REQUIRE(csv::internals::to_string(10.0) == "10.0"); REQUIRE(csv::internals::to_string(100.0) == "100.0"); @@ -456,3 +450,38 @@ TEST_CASE("DataFrame - Write with Sparse Overlay", "[test_dataframe_sparse_overl REQUIRE(result.find("Everything is fine in production") != std::string::npos); // quote updated } //! [DataFrame Sparse Overlay Write Example] + +TEST_CASE("Signed integer conversion - extrema regression", "[test_convert_number][regression]") { + SECTION("Negative integers") { + // Negating the minimum signed value used to overflow before conversion. + const int min_int = (std::numeric_limits::min)(); + const long long min_long_long = (std::numeric_limits::min)(); + + REQUIRE(csv::internals::to_string(-1) == "-1"); + REQUIRE(csv::internals::to_string(min_int) == std::to_string(min_int)); + REQUIRE(csv::internals::to_string(min_long_long) == std::to_string(min_long_long)); + } + + SECTION("Non-negative integers") { + // Converting through size_t can truncate long long on 32-bit platforms. + const long long max_long_long = (std::numeric_limits::max)(); + + REQUIRE(csv::internals::to_string(0) == "0"); + REQUIRE(csv::internals::to_string(0LL) == "0"); + REQUIRE(csv::internals::to_string(max_long_long) == std::to_string(max_long_long)); + } +} + +TEST_CASE("CSV Writer - signed integer extrema regression", "[test_csv_tuple][regression]") { + // Exercise the public writer API so extrema stay intact in a mixed tuple. + const int min_int = (std::numeric_limits::min)(); + const long long min_long_long = (std::numeric_limits::min)(); + const long long max_long_long = (std::numeric_limits::max)(); + std::stringstream output, correct; + auto writer = make_csv_writer(output); + + writer << std::make_tuple(min_int, min_long_long, -1, 0, max_long_long); + correct << min_int << ',' << min_long_long << ",-1,0," << max_long_long << '\n'; + + REQUIRE(output.str() == correct.str()); +}