| /* | |
| * 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 |