| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include "iceberg/util/decimal.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <bit> |
| #include <cstdint> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "iceberg/test/matchers.h" |
| #include "iceberg/util/int128.h" |
| |
| namespace iceberg { |
| |
| namespace { |
| |
| void AssertDecimalFromString(const std::string& s, const Decimal& expected, |
| int32_t expected_precision, int32_t expected_scale) { |
| int32_t precision = 0; |
| int32_t scale = 0; |
| auto result = Decimal::FromString(s, &precision, &scale); |
| EXPECT_THAT(result, IsOk()); |
| const Decimal& actual = result.value(); |
| EXPECT_EQ(expected, actual); |
| EXPECT_EQ(expected_precision, precision); |
| EXPECT_EQ(expected_scale, scale); |
| } |
| |
| } // namespace |
| |
| TEST(DecimalTest, Basics) { |
| AssertDecimalFromString("234.23445", Decimal(23423445), 8, 5); |
| |
| std::string string_value("-23049223942343532412"); |
| Decimal result(string_value); |
| Decimal expected(static_cast<int64_t>(-230492239423435324)); |
| ASSERT_EQ(result, expected * 100 - 12); |
| ASSERT_NE(result.high(), 0); |
| |
| result = Decimal("-23049223942343.532412"); |
| ASSERT_EQ(result, expected * 100 - 12); |
| ASSERT_NE(result.high(), 0); |
| } |
| |
| TEST(DecimalTest, StringStartingWithSign) { |
| AssertDecimalFromString("+234.567", Decimal(234567), 6, 3); |
| AssertDecimalFromString("+2342394230592.232349023094", |
| Decimal("2342394230592232349023094"), 25, 12); |
| AssertDecimalFromString("-234.567", Decimal("-234567"), 6, 3); |
| AssertDecimalFromString("-2342394230592.232349023094", |
| Decimal("-2342394230592232349023094"), 25, 12); |
| } |
| |
| TEST(DecimalTest, StringWithLeadingZeros) { |
| AssertDecimalFromString("0000000000000000000000000000000.234", Decimal(234), 3, 3); |
| AssertDecimalFromString("0000000000000000000000000000000.23400", Decimal(23400), 5, 5); |
| AssertDecimalFromString("234.00", Decimal(23400), 5, 2); |
| AssertDecimalFromString("234.0", Decimal(2340), 4, 1); |
| AssertDecimalFromString("0000000", Decimal(0), 0, 0); |
| AssertDecimalFromString("000.0000", Decimal(0), 4, 4); |
| AssertDecimalFromString(".00000", Decimal(0), 5, 5); |
| } |
| |
| TEST(DecimalTest, DecimalWithExponent) { |
| AssertDecimalFromString("1E1", Decimal(10), 2, 0); |
| AssertDecimalFromString("234.23445e2", Decimal(23423445), 8, 3); |
| AssertDecimalFromString("234.23445e-2", Decimal(23423445), 8, 7); |
| AssertDecimalFromString("234.23445E2", Decimal(23423445), 8, 3); |
| AssertDecimalFromString("234.23445E-2", Decimal(23423445), 8, 7); |
| AssertDecimalFromString("1.23E-8", Decimal(123), 3, 10); |
| } |
| |
| TEST(DecimalTest, SmallValues) { |
| struct TestValue { |
| std::string s; |
| int64_t expected; |
| int32_t expected_precision; |
| int32_t expected_scale; |
| }; |
| |
| for (const auto& tv : std::vector<TestValue>{ |
| {.s = "12.3", .expected = 123LL, .expected_precision = 3, .expected_scale = 1}, |
| {.s = "0.00123", |
| .expected = 123LL, |
| .expected_precision = 5, |
| .expected_scale = 5}, |
| {.s = "1.23E-8", |
| .expected = 123LL, |
| .expected_precision = 3, |
| .expected_scale = 10}, |
| {.s = "-1.23E-8", |
| .expected = -123LL, |
| .expected_precision = 3, |
| .expected_scale = 10}, |
| {.s = "1.23E+3", |
| .expected = 1230LL, |
| .expected_precision = 4, |
| .expected_scale = 0}, |
| {.s = "-1.23E+3", |
| .expected = -1230LL, |
| .expected_precision = 4, |
| .expected_scale = 0}, |
| {.s = "1.23E+5", |
| .expected = 123000LL, |
| .expected_precision = 6, |
| .expected_scale = 0}, |
| {.s = "1.2345E+7", |
| .expected = 12345000LL, |
| .expected_precision = 8, |
| .expected_scale = 0}, |
| {.s = "1.23e-8", |
| .expected = 123LL, |
| .expected_precision = 3, |
| .expected_scale = 10}, |
| {.s = "-1.23e-8", |
| .expected = -123LL, |
| .expected_precision = 3, |
| .expected_scale = 10}, |
| {.s = "1.23e+3", |
| .expected = 1230LL, |
| .expected_precision = 4, |
| .expected_scale = 0}, |
| {.s = "-1.23e+3", |
| .expected = -1230LL, |
| .expected_precision = 4, |
| .expected_scale = 0}, |
| {.s = "1.23e+5", |
| .expected = 123000LL, |
| .expected_precision = 6, |
| .expected_scale = 0}, |
| {.s = "1.2345e+7", |
| .expected = 12345000LL, |
| .expected_precision = 8, |
| .expected_scale = 0}}) { |
| AssertDecimalFromString(tv.s, Decimal(tv.expected), tv.expected_precision, |
| tv.expected_scale); |
| } |
| } |
| |
| TEST(DecimalTest, LargeValues) { |
| const std::array<std::string, 4> string_values = { |
| "99999999999999999999999999999999999999", "-99999999999999999999999999999999999999", |
| "170141183460469231731687303715884105727", // maximum positive value |
| "-170141183460469231731687303715884105728" // minimum negative value |
| }; |
| |
| for (const auto& s : string_values) { |
| const Decimal value(s); |
| const std::string printed_value = value.ToIntegerString(); |
| EXPECT_EQ(printed_value, s) << "Expected: " << s << ", but got: " << printed_value; |
| } |
| } |
| |
| TEST(DecimalTest, TestStringRoundTrip) { |
| static constexpr std::array<uint64_t, 11> kTestBits = { |
| 0, |
| 1, |
| 999, |
| 1000, |
| std::numeric_limits<int32_t>::max(), |
| (1ull << 31), |
| std::numeric_limits<uint32_t>::max(), |
| (1ull << 32), |
| std::numeric_limits<int64_t>::max(), |
| (1ull << 63), |
| std::numeric_limits<uint64_t>::max(), |
| }; |
| static constexpr std::array<int32_t, 3> kScales = {0, 1, 10}; |
| for (uint64_t high : kTestBits) { |
| for (uint64_t low : kTestBits) { |
| Decimal value(high, low); |
| for (int32_t scale : kScales) { |
| auto result = value.ToString(scale); |
| |
| ASSERT_THAT(result, IsOk()) |
| << "Failed to convert Decimal to string: " << value.ToIntegerString() |
| << ", scale: " << scale; |
| |
| auto round_trip = Decimal::FromString(result.value()); |
| ASSERT_THAT(round_trip, IsOk()) |
| << "Failed to convert string back to Decimal: " << result.value(); |
| |
| EXPECT_EQ(value, round_trip.value()) |
| << "Round trip failed for value: " << value.ToIntegerString() |
| << ", scale: " << scale; |
| } |
| } |
| } |
| } |
| |
| TEST(DecimalTest, FromStringLimits) { |
| AssertDecimalFromString("1e37", Decimal(542101086242752217ULL, 68739955140067328ULL), |
| 38, 0); |
| |
| AssertDecimalFromString( |
| "-1e37", Decimal(17904642987466799398ULL, 18378004118569484288ULL), 38, 0); |
| AssertDecimalFromString( |
| "9.87e37", Decimal(5350537721215964381ULL, 15251391175463010304ULL), 38, 0); |
| AssertDecimalFromString( |
| "-9.87e37", Decimal(13096206352493587234ULL, 3195352898246541312ULL), 38, 0); |
| AssertDecimalFromString("12345678901234567890123456789012345678", |
| Decimal(669260594276348691ULL, 14143994781733811022ULL), 38, 0); |
| AssertDecimalFromString("-12345678901234567890123456789012345678", |
| Decimal(17777483479433202924ULL, 4302749291975740594ULL), 38, |
| 0); |
| |
| // "9..9" (38 times) |
| const auto dec38times9pos = Decimal(5421010862427522170ULL, 687399551400673279ULL); |
| // "-9..9" (38 times) |
| const auto dec38times9neg = Decimal(13025733211282029445ULL, 17759344522308878337ULL); |
| |
| AssertDecimalFromString("99999999999999999999999999999999999999", dec38times9pos, 38, |
| 0); |
| AssertDecimalFromString("-99999999999999999999999999999999999999", dec38times9neg, 38, |
| 0); |
| AssertDecimalFromString("9.9999999999999999999999999999999999999e37", dec38times9pos, |
| 38, 0); |
| AssertDecimalFromString("-9.9999999999999999999999999999999999999e37", dec38times9neg, |
| 38, 0); |
| |
| // No exponent, many fractional digits |
| AssertDecimalFromString("9.9999999999999999999999999999999999999", dec38times9pos, 38, |
| 37); |
| AssertDecimalFromString("-9.9999999999999999999999999999999999999", dec38times9neg, 38, |
| 37); |
| AssertDecimalFromString("0.99999999999999999999999999999999999999", dec38times9pos, 38, |
| 38); |
| AssertDecimalFromString("-0.99999999999999999999999999999999999999", dec38times9neg, 38, |
| 38); |
| |
| // Negative exponent |
| AssertDecimalFromString("1e-38", Decimal(0, 1), 1, 38); |
| AssertDecimalFromString( |
| "-1e-38", Decimal(18446744073709551615ULL, 18446744073709551615ULL), 1, 38); |
| AssertDecimalFromString("9.99e-36", Decimal(0, 999), 3, 38); |
| AssertDecimalFromString( |
| "-9.99e-36", Decimal(18446744073709551615ULL, 18446744073709550617ULL), 3, 38); |
| AssertDecimalFromString("987e-38", Decimal(0, 987), 3, 38); |
| AssertDecimalFromString( |
| "-987e-38", Decimal(18446744073709551615ULL, 18446744073709550629ULL), 3, 38); |
| AssertDecimalFromString("99999999999999999999999999999999999999e-37", dec38times9pos, |
| 38, 37); |
| AssertDecimalFromString("-99999999999999999999999999999999999999e-37", dec38times9neg, |
| 38, 37); |
| AssertDecimalFromString("99999999999999999999999999999999999999e-38", dec38times9pos, |
| 38, 38); |
| AssertDecimalFromString("-99999999999999999999999999999999999999e-38", dec38times9neg, |
| 38, 38); |
| } |
| |
| TEST(DecimalTest, FromStringInvalid) { |
| // Empty string |
| auto result = Decimal::FromString(""); |
| ASSERT_THAT(result, IsError(ErrorKind::kInvalidArgument)); |
| ASSERT_THAT(result, HasErrorMessage("Empty string is not a valid Decimal")); |
| for (const auto& invalid_string : |
| std::vector<std::string>{"-", "0.0.0", "0-13-32", "a", "-23092.235-", |
| "-+23092.235", "+-23092.235", "00a", "1e1a", "0.00123D/3", |
| "1.23eA8", "1.23E+3A", "-1.23E--5", "1.2345E+++07"}) { |
| auto result = Decimal::FromString(invalid_string); |
| ASSERT_THAT(result, IsError(ErrorKind::kInvalidArgument)); |
| ASSERT_THAT(result, HasErrorMessage("Invalid decimal string")); |
| } |
| |
| for (const auto& invalid_string : |
| std::vector<std::string>{"1e39", "-1e39", "9e39", "-9e39", "9.9e40", "-9.9e40"}) { |
| auto result = Decimal::FromString(invalid_string); |
| ASSERT_THAT(result, IsError(ErrorKind::kInvalidArgument)); |
| ASSERT_THAT(result, HasErrorMessage("scale must be in the range")); |
| } |
| } |
| |
| TEST(DecimalTest, Division) { |
| const std::string expected_string_value("-23923094039234029"); |
| const Decimal value(expected_string_value); |
| const Decimal result(value / 3); |
| const Decimal expected_value("-7974364679744676"); |
| ASSERT_EQ(expected_value, result); |
| } |
| |
| TEST(DecimalTest, ToString) { |
| struct ToStringCase { |
| int64_t test_value; |
| int32_t scale; |
| const char* expected_string; |
| }; |
| |
| for (const auto& t : std::vector<ToStringCase>{ |
| {.test_value = 0, .scale = -1, .expected_string = "0E+1"}, |
| {.test_value = 0, .scale = 0, .expected_string = "0"}, |
| {.test_value = 0, .scale = 1, .expected_string = "0.0"}, |
| {.test_value = 0, .scale = 6, .expected_string = "0.000000"}, |
| {.test_value = 2, .scale = 7, .expected_string = "2E-7"}, |
| {.test_value = 2, .scale = -1, .expected_string = "2E+1"}, |
| {.test_value = 2, .scale = 0, .expected_string = "2"}, |
| {.test_value = 2, .scale = 1, .expected_string = "0.2"}, |
| {.test_value = 2, .scale = 6, .expected_string = "0.000002"}, |
| {.test_value = -2, .scale = 7, .expected_string = "-2E-7"}, |
| {.test_value = -2, .scale = 7, .expected_string = "-2E-7"}, |
| {.test_value = -2, .scale = -1, .expected_string = "-2E+1"}, |
| {.test_value = -2, .scale = 0, .expected_string = "-2"}, |
| {.test_value = -2, .scale = 1, .expected_string = "-0.2"}, |
| {.test_value = -2, .scale = 6, .expected_string = "-0.000002"}, |
| {.test_value = -2, .scale = 7, .expected_string = "-2E-7"}, |
| {.test_value = 123, .scale = -3, .expected_string = "1.23E+5"}, |
| {.test_value = 123, .scale = -1, .expected_string = "1.23E+3"}, |
| {.test_value = 123, .scale = 1, .expected_string = "12.3"}, |
| {.test_value = 123, .scale = 0, .expected_string = "123"}, |
| {.test_value = 123, .scale = 5, .expected_string = "0.00123"}, |
| {.test_value = 123, .scale = 8, .expected_string = "0.00000123"}, |
| {.test_value = 123, .scale = 9, .expected_string = "1.23E-7"}, |
| {.test_value = 123, .scale = 10, .expected_string = "1.23E-8"}, |
| {.test_value = -123, .scale = -3, .expected_string = "-1.23E+5"}, |
| {.test_value = -123, .scale = -1, .expected_string = "-1.23E+3"}, |
| {.test_value = -123, .scale = 1, .expected_string = "-12.3"}, |
| {.test_value = -123, .scale = 0, .expected_string = "-123"}, |
| {.test_value = -123, .scale = 5, .expected_string = "-0.00123"}, |
| {.test_value = -123, .scale = 8, .expected_string = "-0.00000123"}, |
| {.test_value = -123, .scale = 9, .expected_string = "-1.23E-7"}, |
| {.test_value = -123, .scale = 10, .expected_string = "-1.23E-8"}, |
| {.test_value = 1000000000, .scale = -3, .expected_string = "1.000000000E+12"}, |
| {.test_value = 1000000000, .scale = -1, .expected_string = "1.000000000E+10"}, |
| {.test_value = 1000000000, .scale = 0, .expected_string = "1000000000"}, |
| {.test_value = 1000000000, .scale = 1, .expected_string = "100000000.0"}, |
| {.test_value = 1000000000, .scale = 5, .expected_string = "10000.00000"}, |
| {.test_value = 1000000000, |
| .scale = 15, |
| .expected_string = "0.000001000000000"}, |
| {.test_value = 1000000000, .scale = 16, .expected_string = "1.000000000E-7"}, |
| {.test_value = 1000000000, .scale = 17, .expected_string = "1.000000000E-8"}, |
| {.test_value = -1000000000, |
| .scale = -3, |
| .expected_string = "-1.000000000E+12"}, |
| {.test_value = -1000000000, |
| .scale = -1, |
| .expected_string = "-1.000000000E+10"}, |
| {.test_value = -1000000000, .scale = 0, .expected_string = "-1000000000"}, |
| {.test_value = -1000000000, .scale = 1, .expected_string = "-100000000.0"}, |
| {.test_value = -1000000000, .scale = 5, .expected_string = "-10000.00000"}, |
| {.test_value = -1000000000, |
| .scale = 15, |
| .expected_string = "-0.000001000000000"}, |
| {.test_value = -1000000000, .scale = 16, .expected_string = "-1.000000000E-7"}, |
| {.test_value = -1000000000, .scale = 17, .expected_string = "-1.000000000E-8"}, |
| {.test_value = 1234567890123456789LL, |
| .scale = -3, |
| .expected_string = "1.234567890123456789E+21"}, |
| {.test_value = 1234567890123456789LL, |
| .scale = -1, |
| .expected_string = "1.234567890123456789E+19"}, |
| {.test_value = 1234567890123456789LL, |
| .scale = 0, |
| .expected_string = "1234567890123456789"}, |
| {.test_value = 1234567890123456789LL, |
| .scale = 1, |
| .expected_string = "123456789012345678.9"}, |
| {.test_value = 1234567890123456789LL, |
| .scale = 5, |
| .expected_string = "12345678901234.56789"}, |
| {.test_value = 1234567890123456789LL, |
| .scale = 24, |
| .expected_string = "0.000001234567890123456789"}, |
| {.test_value = 1234567890123456789LL, |
| .scale = 25, |
| .expected_string = "1.234567890123456789E-7"}, |
| {.test_value = -1234567890123456789LL, |
| .scale = -3, |
| .expected_string = "-1.234567890123456789E+21"}, |
| {.test_value = -1234567890123456789LL, |
| .scale = -1, |
| .expected_string = "-1.234567890123456789E+19"}, |
| {.test_value = -1234567890123456789LL, |
| .scale = 0, |
| .expected_string = "-1234567890123456789"}, |
| {.test_value = -1234567890123456789LL, |
| .scale = 1, |
| .expected_string = "-123456789012345678.9"}, |
| {.test_value = -1234567890123456789LL, |
| .scale = 5, |
| .expected_string = "-12345678901234.56789"}, |
| {.test_value = -1234567890123456789LL, |
| .scale = 24, |
| .expected_string = "-0.000001234567890123456789"}, |
| {.test_value = -1234567890123456789LL, |
| .scale = 25, |
| .expected_string = "-1.234567890123456789E-7"}, |
| }) { |
| const Decimal value(t.test_value); |
| auto result = value.ToString(t.scale); |
| ASSERT_THAT(result, IsOk()) |
| << "Failed to convert Decimal to string: " << value.ToIntegerString() |
| << ", scale: " << t.scale; |
| |
| EXPECT_EQ(result.value(), t.expected_string) |
| << "Expected: " << t.expected_string << ", but got: " << result.value(); |
| } |
| } |
| |
| TEST(DecimalTest, FromBigEndian) { |
| // We test out a variety of scenarios: |
| // |
| // * Positive values that are left shifted |
| // and filled in with the same bit pattern |
| // * Negated of the positive values |
| // * Complement of the positive values |
| // |
| // For the positive values, we can call FromBigEndian |
| // with a length that is less than 16, whereas we must |
| // pass all 16 bytes for the negative and complement. |
| // |
| // We use a number of bit patterns to increase the coverage |
| // of scenarios |
| constexpr int WidthMinusOne = Decimal::kByteWidth - 1; |
| |
| for (int32_t start : {1, 15, /* 00001111 */ |
| 85, /* 01010101 */ |
| 127 /* 01111111 */}) { |
| Decimal value(start); |
| for (int ii = 0; ii < Decimal::kByteWidth; ++ii) { |
| auto native_endian = value.ToBytes(); |
| if constexpr (std::endian::native == std::endian::little) { |
| // convert to big endian |
| std::ranges::reverse(native_endian); |
| } |
| // Limit the number of bytes we are passing to make |
| // sure that it works correctly. That's why all of the |
| // 'start' values don't have a 1 in the most significant |
| // bit place |
| auto result = |
| Decimal::FromBigEndian(native_endian.data() + WidthMinusOne - ii, ii + 1); |
| ASSERT_THAT(result, IsOk()); |
| const Decimal& decimal = result.value(); |
| EXPECT_EQ(decimal, value); |
| |
| // Negate it |
| auto negated = -value; |
| native_endian = negated.ToBytes(); |
| |
| if constexpr (std::endian::native == std::endian::little) { |
| // convert to big endian |
| std::ranges::reverse(native_endian); |
| } |
| |
| result = Decimal::FromBigEndian(native_endian.data() + WidthMinusOne - ii, ii + 1); |
| ASSERT_THAT(result, IsOk()); |
| const Decimal& negated_decimal = result.value(); |
| EXPECT_EQ(negated_decimal, negated); |
| |
| // Take the complement |
| auto complement = ~value; |
| native_endian = complement.ToBytes(); |
| |
| if constexpr (std::endian::native == std::endian::little) { |
| // convert to big endian |
| std::ranges::reverse(native_endian); |
| } |
| result = Decimal::FromBigEndian(native_endian.data(), Decimal::kByteWidth); |
| ASSERT_THAT(result, IsOk()); |
| const Decimal& complement_decimal = result.value(); |
| EXPECT_EQ(complement_decimal, complement); |
| |
| value <<= 2; |
| value += Decimal(start); |
| } |
| } |
| } |
| |
| TEST(DecimalTest, FromBigEndianInvalid) { |
| ASSERT_THAT(Decimal::FromBigEndian(nullptr, -1), IsError(ErrorKind::kInvalidArgument)); |
| ASSERT_THAT(Decimal::FromBigEndian(nullptr, Decimal::kByteWidth + 1), |
| IsError(ErrorKind::kInvalidArgument)); |
| } |
| |
| TEST(DecimalTest, ToBigEndian) { |
| std::vector<int64_t> high_values = {0, |
| 1, |
| -1, |
| INT32_MAX, |
| INT32_MIN, |
| static_cast<int64_t>(INT32_MAX) + 1, |
| static_cast<int64_t>(INT32_MIN) - 1, |
| INT64_MAX, |
| INT64_MIN}; |
| std::vector<uint64_t> low_values = {0, |
| 1, |
| 255, |
| UINT32_MAX, |
| static_cast<uint64_t>(UINT32_MAX) + 1, |
| static_cast<uint64_t>(UINT32_MAX) + 2, |
| static_cast<uint64_t>(UINT32_MAX) + 3, |
| static_cast<uint64_t>(UINT32_MAX) + 4, |
| static_cast<uint64_t>(UINT32_MAX) + 5, |
| static_cast<uint64_t>(UINT32_MAX) + 6, |
| static_cast<uint64_t>(UINT32_MAX) + 7, |
| static_cast<uint64_t>(UINT32_MAX) + 8, |
| UINT64_MAX}; |
| |
| for (int64_t high : high_values) { |
| for (uint64_t low : low_values) { |
| Decimal decimal(high, low); |
| auto bytes = decimal.ToBigEndian(); |
| auto result = Decimal::FromBigEndian(bytes.data(), bytes.size()); |
| ASSERT_THAT(result, IsOk()); |
| EXPECT_EQ(result.value(), decimal); |
| } |
| } |
| |
| for (int128_t value : std::vector<int128_t>{-INT64_MAX, -INT32_MAX, -255, -1, 0, 1, 255, |
| 256, INT32_MAX, INT64_MAX}) { |
| Decimal decimal(value); |
| auto bytes = decimal.ToBigEndian(); |
| auto result = Decimal::FromBigEndian(bytes.data(), bytes.size()); |
| ASSERT_THAT(result, IsOk()); |
| EXPECT_EQ(result.value(), decimal); |
| } |
| } |
| |
| TEST(DecimalTestFunctionality, Multiply) { |
| ASSERT_EQ(Decimal(60501), Decimal(301) * Decimal(201)); |
| ASSERT_EQ(Decimal(-60501), Decimal(-301) * Decimal(201)); |
| ASSERT_EQ(Decimal(-60501), Decimal(301) * Decimal(-201)); |
| ASSERT_EQ(Decimal(60501), Decimal(-301) * Decimal(-201)); |
| |
| // Edge cases |
| for (auto x : std::vector<int128_t>{-INT64_MAX, -INT32_MAX, 0, INT32_MAX, INT64_MAX}) { |
| for (auto y : |
| std::vector<int128_t>{-INT32_MAX, -32, -2, -1, 0, 1, 2, 32, INT32_MAX}) { |
| Decimal decimal_x(x); |
| Decimal decimal_y(y); |
| Decimal result = decimal_x * decimal_y; |
| EXPECT_EQ(Decimal(x * y), result) << " x: " << decimal_x << " y: " << decimal_y; |
| } |
| } |
| } |
| |
| TEST(DecimalTestFunctionality, Divide) { |
| ASSERT_EQ(Decimal(66), Decimal(20100) / Decimal(301)); |
| ASSERT_EQ(Decimal(-66), Decimal(-20100) / Decimal(301)); |
| ASSERT_EQ(Decimal(-66), Decimal(20100) / Decimal(-301)); |
| ASSERT_EQ(Decimal(66), Decimal(-20100) / Decimal(-301)); |
| |
| for (auto x : std::vector<int128_t>{-INT64_MAX, -INT32_MAX, 0, INT32_MAX, INT64_MAX}) { |
| for (auto y : std::vector<int128_t>{-INT32_MAX, -32, -2, -1, 1, 2, 32, INT32_MAX}) { |
| Decimal decimal_x(x); |
| Decimal decimal_y(y); |
| Decimal result = decimal_x / decimal_y; |
| EXPECT_EQ(Decimal(x / y), result) << " x: " << decimal_x << " y: " << decimal_y; |
| } |
| } |
| } |
| |
| TEST(DecimalTestFunctionality, Modulo) { |
| ASSERT_EQ(Decimal(234), Decimal(20100) % Decimal(301)); |
| ASSERT_EQ(Decimal(-234), Decimal(-20100) % Decimal(301)); |
| ASSERT_EQ(Decimal(234), Decimal(20100) % Decimal(-301)); |
| ASSERT_EQ(Decimal(-234), Decimal(-20100) % Decimal(-301)); |
| |
| // Test some edge cases |
| for (auto x : std::vector<int128_t>{-INT64_MAX, -INT32_MAX, 0, INT32_MAX, INT64_MAX}) { |
| for (auto y : std::vector<int128_t>{-INT32_MAX, -32, -2, -1, 1, 2, 32, INT32_MAX}) { |
| Decimal decimal_x(x); |
| Decimal decimal_y(y); |
| Decimal result = decimal_x % decimal_y; |
| EXPECT_EQ(Decimal(x % y), result) << " x: " << decimal_x << " y: " << decimal_y; |
| } |
| } |
| } |
| |
| TEST(DecimalTestFunctionality, Sign) { |
| ASSERT_EQ(1, Decimal(999999).Sign()); |
| ASSERT_EQ(-1, Decimal(-999999).Sign()); |
| ASSERT_EQ(1, Decimal(0).Sign()); |
| } |
| |
| TEST(DecimalTestFunctionality, FitsInPrecision) { |
| ASSERT_TRUE(Decimal("0").FitsInPrecision(1)); |
| ASSERT_TRUE(Decimal("9").FitsInPrecision(1)); |
| ASSERT_TRUE(Decimal("-9").FitsInPrecision(1)); |
| ASSERT_FALSE(Decimal("10").FitsInPrecision(1)); |
| ASSERT_FALSE(Decimal("-10").FitsInPrecision(1)); |
| |
| ASSERT_TRUE(Decimal("0").FitsInPrecision(2)); |
| ASSERT_TRUE(Decimal("10").FitsInPrecision(2)); |
| ASSERT_TRUE(Decimal("-10").FitsInPrecision(2)); |
| ASSERT_TRUE(Decimal("99").FitsInPrecision(2)); |
| ASSERT_TRUE(Decimal("-99").FitsInPrecision(2)); |
| ASSERT_FALSE(Decimal("100").FitsInPrecision(2)); |
| ASSERT_FALSE(Decimal("-100").FitsInPrecision(2)); |
| |
| std::string max_nines(Decimal::kMaxPrecision, '9'); |
| ASSERT_TRUE(Decimal(max_nines).FitsInPrecision(Decimal::kMaxPrecision)); |
| ASSERT_TRUE(Decimal("-" + max_nines).FitsInPrecision(Decimal::kMaxPrecision)); |
| |
| std::string max_zeros(Decimal::kMaxPrecision, '0'); |
| ASSERT_FALSE(Decimal("1" + max_zeros).FitsInPrecision(Decimal::kMaxPrecision)); |
| ASSERT_FALSE(Decimal("-1" + max_zeros).FitsInPrecision(Decimal::kMaxPrecision)); |
| } |
| |
| TEST(DecimalTest, LeftShift) { |
| auto check = [](int128_t x, uint32_t bits) { |
| auto expected = Decimal(x << bits); |
| auto actual = Decimal(x) << bits; |
| ASSERT_EQ(actual.low(), expected.low()); |
| ASSERT_EQ(actual.high(), expected.high()); |
| }; |
| |
| ASSERT_EQ(Decimal("0"), Decimal("0") << 0); |
| ASSERT_EQ(Decimal("0"), Decimal("0") << 1); |
| ASSERT_EQ(Decimal("0"), Decimal("0") << 63); |
| ASSERT_EQ(Decimal("0"), Decimal("0") << 127); |
| |
| check(123, 0); |
| check(123, 1); |
| check(123, 63); |
| check(123, 64); |
| check(123, 120); |
| |
| ASSERT_EQ(Decimal("199999999999998"), Decimal("99999999999999") << 1); |
| ASSERT_EQ(Decimal("3435973836799965640261632"), Decimal("99999999999999") << 35); |
| ASSERT_EQ(Decimal("120892581961461708544797985370825293824"), Decimal("99999999999999") |
| << 80); |
| |
| ASSERT_EQ(Decimal("1234567890123456789012"), Decimal("1234567890123456789012") << 0); |
| ASSERT_EQ(Decimal("2469135780246913578024"), Decimal("1234567890123456789012") << 1); |
| ASSERT_EQ(Decimal("88959991838777271103427858320412639232"), |
| Decimal("1234567890123456789012") << 56); |
| |
| check(-123, 0); |
| check(-123, 1); |
| check(-123, 63); |
| check(-123, 64); |
| check(-123, 120); |
| |
| ASSERT_EQ(Decimal("-199999999999998"), Decimal("-99999999999999") << 1); |
| ASSERT_EQ(Decimal("-3435973836799965640261632"), Decimal("-99999999999999") << 35); |
| ASSERT_EQ(Decimal("-120892581961461708544797985370825293824"), |
| Decimal("-99999999999999") << 80); |
| |
| ASSERT_EQ(Decimal("-1234567890123456789012"), Decimal("-1234567890123456789012") << 0); |
| ASSERT_EQ(Decimal("-2469135780246913578024"), Decimal("-1234567890123456789012") << 1); |
| ASSERT_EQ(Decimal("-88959991838777271103427858320412639232"), |
| Decimal("-1234567890123456789012") << 56); |
| } |
| |
| TEST(DecimalTest, RightShift) { |
| ASSERT_EQ(Decimal("0"), Decimal("0") >> 0); |
| ASSERT_EQ(Decimal("0"), Decimal("0") >> 1); |
| ASSERT_EQ(Decimal("0"), Decimal("0") >> 63); |
| ASSERT_EQ(Decimal("0"), Decimal("0") >> 127); |
| |
| ASSERT_EQ(Decimal("1"), Decimal("1") >> 0); |
| ASSERT_EQ(Decimal("0"), Decimal("1") >> 1); |
| ASSERT_EQ(Decimal("0"), Decimal("1") >> 63); |
| ASSERT_EQ(Decimal("0"), Decimal("1") >> 127); |
| |
| ASSERT_EQ(Decimal("-1"), Decimal("-1") >> 0); |
| ASSERT_EQ(Decimal("-1"), Decimal("-1") >> 1); |
| ASSERT_EQ(Decimal("-1"), Decimal("-1") >> 63); |
| ASSERT_EQ(Decimal("-1"), Decimal("-1") >> 127); |
| |
| ASSERT_EQ(Decimal("1096516"), Decimal("1234567890123456789012") >> 50); |
| ASSERT_EQ(Decimal("66"), Decimal("1234567890123456789012") >> 64); |
| ASSERT_EQ(Decimal("2"), Decimal("1234567890123456789012") >> 69); |
| ASSERT_EQ(Decimal("0"), Decimal("1234567890123456789012") >> 71); |
| ASSERT_EQ(Decimal("0"), Decimal("1234567890123456789012") >> 127); |
| |
| ASSERT_EQ(Decimal("-1096517"), Decimal("-1234567890123456789012") >> 50); |
| ASSERT_EQ(Decimal("-67"), Decimal("-1234567890123456789012") >> 64); |
| ASSERT_EQ(Decimal("-3"), Decimal("-1234567890123456789012") >> 69); |
| ASSERT_EQ(Decimal("-1"), Decimal("-1234567890123456789012") >> 71); |
| ASSERT_EQ(Decimal("-1"), Decimal("-1234567890123456789012") >> 127); |
| } |
| |
| TEST(DecimalTest, Negate) { |
| auto check = [](Decimal pos, Decimal neg) { |
| EXPECT_EQ(-pos, neg); |
| EXPECT_EQ(-neg, pos); |
| }; |
| |
| check(Decimal(0, 0), Decimal(0, 0)); |
| check(Decimal(0, 1), Decimal(-1, 0xFFFFFFFFFFFFFFFFULL)); |
| check(Decimal(0, 2), Decimal(-1, 0xFFFFFFFFFFFFFFFEULL)); |
| check(Decimal(0, 0x8000000000000000ULL), Decimal(-1, 0x8000000000000000ULL)); |
| check(Decimal(0, 0xFFFFFFFFFFFFFFFFULL), Decimal(-1, 1)); |
| check(Decimal(12, 0), Decimal(-12, 0)); |
| check(Decimal(12, 1), Decimal(-13, 0xFFFFFFFFFFFFFFFFULL)); |
| check(Decimal(12, 0xFFFFFFFFFFFFFFFFULL), Decimal(-13, 1)); |
| } |
| |
| TEST(DecimalTest, Rescale) { |
| ASSERT_EQ(Decimal(11100), Decimal(111).Rescale(0, 2).value()); |
| ASSERT_EQ(Decimal(111), Decimal(11100).Rescale(2, 0).value()); |
| ASSERT_EQ(Decimal(5), Decimal(500000).Rescale(6, 1).value()); |
| ASSERT_EQ(Decimal(500000), Decimal(5).Rescale(1, 6).value()); |
| |
| ASSERT_THAT(Decimal(5555555).Rescale(6, 1), IsError(ErrorKind::kInvalid)); |
| } |
| |
| TEST(DecimalTest, Compare) { |
| // max positive unscaled value |
| // 10^38 - 1 scale cause overflow |
| ASSERT_EQ(Decimal::Compare(Decimal("99999999999999999999999999999999999999"), |
| Decimal("99999999999999999999999999999999999999"), 2, 3), |
| std::partial_ordering::greater); |
| // 10^37 - 1 scale no overflow |
| ASSERT_EQ(Decimal::Compare(Decimal("9999999999999999999999999999999999999"), |
| Decimal("99999999999999999999999999999999999999"), 2, 3), |
| std::partial_ordering::less); |
| |
| // min negative unscaled value |
| // -10^38 + 1 scale cause overflow |
| ASSERT_EQ(Decimal::Compare(Decimal("-99999999999999999999999999999999999999"), |
| Decimal("-99999999999999999999999999999999999999"), 2, 3), |
| std::partial_ordering::less); |
| // -10^37 + 1 scale no overflow |
| ASSERT_EQ(Decimal::Compare(Decimal("-9999999999999999999999999999999999999"), |
| Decimal("-99999999999999999999999999999999999999"), 2, 3), |
| std::partial_ordering::greater); |
| |
| // equal values with different scales |
| ASSERT_EQ(Decimal::Compare(Decimal("123456789"), Decimal("1234567890"), 2, 3), |
| std::partial_ordering::equivalent); |
| ASSERT_EQ(Decimal::Compare(Decimal("-1234567890"), Decimal("-123456789"), 3, 2), |
| std::partial_ordering::equivalent); |
| |
| // different values with different scales |
| ASSERT_EQ(Decimal::Compare(Decimal("123456788"), Decimal("1234567890"), 2, 3), |
| std::partial_ordering::less); |
| ASSERT_EQ(Decimal::Compare(Decimal("-1234567890"), Decimal("-123456788"), 2, 3), |
| std::partial_ordering::less); |
| |
| // different values with same scales |
| ASSERT_EQ(Decimal::Compare(Decimal("123456790"), Decimal("123456789"), 2, 2), |
| std::partial_ordering::greater); |
| ASSERT_EQ(Decimal::Compare(Decimal("-123456790"), Decimal("-123456789"), 2, 2), |
| std::partial_ordering::less); |
| |
| // different signs |
| ASSERT_EQ(Decimal::Compare(Decimal("123456789"), Decimal("-123456789"), 2, 3), |
| std::partial_ordering::greater); |
| ASSERT_EQ(Decimal::Compare(Decimal("-123456789"), Decimal("123456789"), 2, 3), |
| std::partial_ordering::less); |
| |
| // zero comparisons |
| ASSERT_EQ(Decimal::Compare(Decimal("0"), Decimal("0"), 2, 3), |
| std::partial_ordering::equivalent); |
| ASSERT_EQ(Decimal::Compare(Decimal("0"), Decimal("123456789"), 2, 3), |
| std::partial_ordering::less); |
| ASSERT_EQ(Decimal::Compare(Decimal("-123456789"), Decimal("0"), 2, 3), |
| std::partial_ordering::less); |
| } |
| |
| } // namespace iceberg |