From 3c5b4f4f82124969e48ace15c5ce808a335c707a Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Tue, 13 Oct 2026 16:37:16 -0300 Subject: [PATCH] Cover `Decimal` boundary cases and fixed-format `dectest` rows Signed-off-by: Juan Cruz Viotti --- test/json/json_decimal_test.cc | 16 ++++ test/numeric/decimal_ibm_dectest.cc | 10 +++ test/numeric/numeric_decimal_test.cc | 121 +++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) diff --git a/test/json/json_decimal_test.cc b/test/json/json_decimal_test.cc index eb61e8690..52b4803c7 100644 --- a/test/json/json_decimal_test.cc +++ b/test/json/json_decimal_test.cc @@ -1299,3 +1299,19 @@ TEST(divisible_by_across_the_whole_exponent_range_is_false) { sourcemeta::core::Decimal{"3e-2147483648"}}; EXPECT_FALSE(dividend.divisible_by(divisor)); } + +// Spelling the same divisor out in full digits rather than as a power of ten +// takes more room to hold, and the verdict cannot turn on which spelling the +// caller reached for, however far the dividend sits above it +TEST(divisible_by_a_wide_exponent_gap_with_the_divisor_in_full_digits) { + const sourcemeta::core::JSON dividend{sourcemeta::core::Decimal{"1e100"}}; + const sourcemeta::core::JSON divisor{ + sourcemeta::core::Decimal{"10000000000000000000"}}; + EXPECT_TRUE(dividend.divisible_by(divisor)); +} + +TEST(divisible_by_a_wide_exponent_gap_with_the_divisor_in_exponent_form) { + const sourcemeta::core::JSON dividend{sourcemeta::core::Decimal{"1e100"}}; + const sourcemeta::core::JSON divisor{sourcemeta::core::Decimal{"1e19"}}; + EXPECT_TRUE(dividend.divisible_by(divisor)); +} diff --git a/test/numeric/decimal_ibm_dectest.cc b/test/numeric/decimal_ibm_dectest.cc index f257b2a1a..257cc604b 100644 --- a/test/numeric/decimal_ibm_dectest.cc +++ b/test/numeric/decimal_ibm_dectest.cc @@ -622,6 +622,16 @@ static auto should_skip_file(const std::string &filename) -> bool { const auto lower{to_lower(filename)}; if (lower.starts_with("dd") || lower.starts_with("dq") || lower.starts_with("ds")) { + // The fixed-format files set a precision and a rounding this Decimal + // cannot be asked for, so their arithmetic cannot be judged here. Copying, + // comparing and asking whether two operands share a quantum reach no + // context at all, and those families carry rows no other file does + if (lower.find("copy") != std::string::npos || + lower.find("compare") != std::string::npos || + lower.find("samequantum") != std::string::npos) { + return false; + } + return true; } return lower.find("randoms") != std::string::npos || diff --git a/test/numeric/numeric_decimal_test.cc b/test/numeric/numeric_decimal_test.cc index 0aa819411..5c4ed1323 100644 --- a/test/numeric/numeric_decimal_test.cc +++ b/test/numeric/numeric_decimal_test.cc @@ -554,6 +554,31 @@ TEST(to_integral_already_integer) { EXPECT_EQ(value.to_integral(), expected); } +// Rounding up the last nine of a coefficient carries into a digit that was not +// there before, so the result is one digit wider than what it came from +TEST(to_integral_rounds_up_into_an_extra_digit) { + EXPECT_EQ(sourcemeta::core::Decimal{"9.5"}.to_integral(), + sourcemeta::core::Decimal{10}); + EXPECT_EQ(sourcemeta::core::Decimal{"9.9"}.to_integral(), + sourcemeta::core::Decimal{10}); + EXPECT_EQ(sourcemeta::core::Decimal{"99.9"}.to_integral(), + sourcemeta::core::Decimal{100}); +} + +TEST(to_integral_below_the_tie_keeps_the_digit_count) { + EXPECT_EQ(sourcemeta::core::Decimal{"9.4"}.to_integral(), + sourcemeta::core::Decimal{9}); +} + +TEST(to_integral_rounds_a_negative_up_into_an_extra_digit) { + EXPECT_EQ(sourcemeta::core::Decimal{"-9.5"}.to_integral(), + sourcemeta::core::Decimal{-10}); + EXPECT_EQ(sourcemeta::core::Decimal{"-9.9"}.to_integral(), + sourcemeta::core::Decimal{-10}); + EXPECT_EQ(sourcemeta::core::Decimal{"-99.9"}.to_integral(), + sourcemeta::core::Decimal{-100}); +} + TEST(factory_nan) { const auto nan_value{sourcemeta::core::Decimal::nan()}; EXPECT_TRUE(nan_value.is_nan()); @@ -5758,6 +5783,57 @@ TEST(add_negative_scaled_zero_to_positive_zero) { EXPECT_FALSE(result.is_signed()); } +// A sum of zeros is positive unless both operands were negative, and the +// quantum it keeps is the finer of the two, even with one operand stored at the +// top of the exponent range and the other at the bottom +TEST(add_a_negative_zero_far_below_a_zero_gives_a_positive_zero) { + const sourcemeta::core::Decimal left{"0e2147483647"}; + const sourcemeta::core::Decimal right{"-0e-2147483648"}; + const sourcemeta::core::Decimal quantum{"0e-2147483648"}; + const auto forward{left + right}; + const auto reverse{right + left}; + EXPECT_TRUE(forward.is_zero()); + EXPECT_FALSE(forward.is_signed()); + EXPECT_FALSE(forward.is_integer()); + EXPECT_TRUE(forward.same_quantum(quantum)); + EXPECT_TRUE(reverse.is_zero()); + EXPECT_FALSE(reverse.is_signed()); + EXPECT_FALSE(reverse.is_integer()); + EXPECT_TRUE(reverse.same_quantum(quantum)); +} + +TEST(add_a_zero_far_below_a_negative_zero_gives_a_positive_zero) { + const sourcemeta::core::Decimal left{"-0e2147483647"}; + const sourcemeta::core::Decimal right{"0e-2147483648"}; + const sourcemeta::core::Decimal quantum{"0e-2147483648"}; + const auto forward{left + right}; + const auto reverse{right + left}; + EXPECT_TRUE(forward.is_zero()); + EXPECT_FALSE(forward.is_signed()); + EXPECT_FALSE(forward.is_integer()); + EXPECT_TRUE(forward.same_quantum(quantum)); + EXPECT_TRUE(reverse.is_zero()); + EXPECT_FALSE(reverse.is_signed()); + EXPECT_FALSE(reverse.is_integer()); + EXPECT_TRUE(reverse.same_quantum(quantum)); +} + +TEST(add_two_negative_zeros_at_opposite_ends_of_the_exponent_range) { + const sourcemeta::core::Decimal left{"-0e2147483647"}; + const sourcemeta::core::Decimal right{"-0e-2147483648"}; + const sourcemeta::core::Decimal quantum{"0e-2147483648"}; + const auto forward{left + right}; + const auto reverse{right + left}; + EXPECT_TRUE(forward.is_zero()); + EXPECT_TRUE(forward.is_signed()); + EXPECT_FALSE(forward.is_integer()); + EXPECT_TRUE(forward.same_quantum(quantum)); + EXPECT_TRUE(reverse.is_zero()); + EXPECT_TRUE(reverse.is_signed()); + EXPECT_FALSE(reverse.is_integer()); + EXPECT_TRUE(reverse.same_quantum(quantum)); +} + TEST(add_opposite_signs_gives_positive_zero) { const sourcemeta::core::Decimal left{-5}; const sourcemeta::core::Decimal right{5}; @@ -6491,6 +6567,51 @@ TEST(to_integral_of_a_big_value_at_the_bottom_of_the_exponent_range_is_zero) { EXPECT_EQ(result.to_string(), "0"); } +// A decimal point that sits past every digit the coefficient holds leaves no +// digit on the integer side to round towards, and the zero that comes out of +// it keeps the sign it went in with +TEST(to_integral_of_a_zero_scaled_below_the_integer_boundary_is_zero) { + const sourcemeta::core::Decimal value{"0e-18"}; + const auto result{value.to_integral()}; + EXPECT_TRUE(result.is_zero()); + EXPECT_FALSE(result.is_signed()); + EXPECT_EQ(result.to_string(), "0"); +} + +TEST(to_integral_of_a_negative_zero_scaled_down_keeps_its_sign) { + const sourcemeta::core::Decimal value{"-0e-36"}; + const auto result{value.to_integral()}; + EXPECT_TRUE(result.is_zero()); + EXPECT_TRUE(result.is_signed()); + EXPECT_EQ(result.to_string(), "-0"); +} + +TEST(to_integral_of_a_value_below_the_integer_boundary_is_zero) { + const sourcemeta::core::Decimal value{"600000000000000000e-36"}; + const auto result{value.to_integral()}; + EXPECT_TRUE(result.is_zero()); + EXPECT_FALSE(result.is_signed()); + EXPECT_EQ(result.to_string(), "0"); +} + +TEST(to_integral_of_a_negative_below_the_integer_boundary_keeps_its_sign) { + const sourcemeta::core::Decimal value{"-600000000000000000e-36"}; + const auto result{value.to_integral()}; + EXPECT_TRUE(result.is_zero()); + EXPECT_TRUE(result.is_signed()); + EXPECT_EQ(result.to_string(), "-0"); +} + +TEST(to_integral_below_the_integer_boundary_matches_the_exponent_form) { + const sourcemeta::core::Decimal full{"600000000000000000e-36"}; + const sourcemeta::core::Decimal exponential{"6e-19"}; + const auto result{exponential.to_integral()}; + EXPECT_EQ(full.to_integral(), result); + EXPECT_TRUE(result.is_zero()); + EXPECT_FALSE(result.is_signed()); + EXPECT_EQ(result.to_string(), "0"); +} + TEST(dividing_minus_one_by_seven_rounds_half_even) { const sourcemeta::core::Decimal dividend{-1}; const sourcemeta::core::Decimal divisor{7};