|
18 | 18 | */ |
19 | 19 | #include "iceberg/expression/decimal.h" |
20 | 20 |
|
| 21 | +#include <array> |
| 22 | +#include <cstdint> |
| 23 | + |
21 | 24 | #include <gtest/gtest.h> |
| 25 | +#include <sys/types.h> |
22 | 26 |
|
23 | 27 | #include "gmock/gmock.h" |
24 | 28 | #include "matchers.h" |
@@ -164,4 +168,251 @@ TEST(DecimalTest, LargeValues) { |
164 | 168 | } |
165 | 169 | } |
166 | 170 |
|
| 171 | +TEST(DecimalTest, TestStringRoundTrip) { |
| 172 | + static constexpr std::array<uint64_t, 11> kTestBits = { |
| 173 | + 0, |
| 174 | + 1, |
| 175 | + 999, |
| 176 | + 1000, |
| 177 | + std::numeric_limits<int32_t>::max(), |
| 178 | + (1ull << 31), |
| 179 | + std::numeric_limits<uint32_t>::max(), |
| 180 | + (1ull << 32), |
| 181 | + std::numeric_limits<int64_t>::max(), |
| 182 | + (1ull << 63), |
| 183 | + std::numeric_limits<uint64_t>::max(), |
| 184 | + }; |
| 185 | + static constexpr std::array<int32_t, 3> kScales = {0, 1, 10}; |
| 186 | + for (uint64_t high : kTestBits) { |
| 187 | + for (uint64_t low : kTestBits) { |
| 188 | + Decimal value(high, low); |
| 189 | + for (int32_t scale : kScales) { |
| 190 | + auto result = value.ToString(scale); |
| 191 | + |
| 192 | + ASSERT_THAT(result, IsOk()) |
| 193 | + << "Failed to convert Decimal to string: " << value.ToIntegerString() |
| 194 | + << ", scale: " << scale; |
| 195 | + |
| 196 | + auto round_trip = Decimal::FromString(result.value()); |
| 197 | + ASSERT_THAT(round_trip, IsOk()) |
| 198 | + << "Failed to convert string back to Decimal: " << result.value(); |
| 199 | + |
| 200 | + EXPECT_EQ(value, round_trip.value()) |
| 201 | + << "Round trip failed for value: " << value.ToIntegerString() |
| 202 | + << ", scale: " << scale; |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | +} |
| 207 | + |
| 208 | +TEST(DecimalTest, FromStringLimits) { |
| 209 | + AssertDecimalFromString("1e37", Decimal(542101086242752217ULL, 68739955140067328ULL), |
| 210 | + 38, 0); |
| 211 | + |
| 212 | + AssertDecimalFromString( |
| 213 | + "-1e37", Decimal(17904642987466799398ULL, 18378004118569484288ULL), 38, 0); |
| 214 | + AssertDecimalFromString( |
| 215 | + "9.87e37", Decimal(5350537721215964381ULL, 15251391175463010304ULL), 38, 0); |
| 216 | + AssertDecimalFromString( |
| 217 | + "-9.87e37", Decimal(13096206352493587234ULL, 3195352898246541312ULL), 38, 0); |
| 218 | + AssertDecimalFromString("12345678901234567890123456789012345678", |
| 219 | + Decimal(669260594276348691ULL, 14143994781733811022ULL), 38, 0); |
| 220 | + AssertDecimalFromString("-12345678901234567890123456789012345678", |
| 221 | + Decimal(17777483479433202924ULL, 4302749291975740594ULL), 38, |
| 222 | + 0); |
| 223 | + |
| 224 | + // "9..9" (38 times) |
| 225 | + const auto dec38times9pos = Decimal(5421010862427522170ULL, 687399551400673279ULL); |
| 226 | + // "-9..9" (38 times) |
| 227 | + const auto dec38times9neg = Decimal(13025733211282029445ULL, 17759344522308878337ULL); |
| 228 | + |
| 229 | + AssertDecimalFromString("99999999999999999999999999999999999999", dec38times9pos, 38, |
| 230 | + 0); |
| 231 | + AssertDecimalFromString("-99999999999999999999999999999999999999", dec38times9neg, 38, |
| 232 | + 0); |
| 233 | + AssertDecimalFromString("9.9999999999999999999999999999999999999e37", dec38times9pos, |
| 234 | + 38, 0); |
| 235 | + AssertDecimalFromString("-9.9999999999999999999999999999999999999e37", dec38times9neg, |
| 236 | + 38, 0); |
| 237 | + |
| 238 | + // No exponent, many fractional digits |
| 239 | + AssertDecimalFromString("9.9999999999999999999999999999999999999", dec38times9pos, 38, |
| 240 | + 37); |
| 241 | + AssertDecimalFromString("-9.9999999999999999999999999999999999999", dec38times9neg, 38, |
| 242 | + 37); |
| 243 | + AssertDecimalFromString("0.99999999999999999999999999999999999999", dec38times9pos, 38, |
| 244 | + 38); |
| 245 | + AssertDecimalFromString("-0.99999999999999999999999999999999999999", dec38times9neg, 38, |
| 246 | + 38); |
| 247 | + |
| 248 | + // Negative exponent |
| 249 | + AssertDecimalFromString("1e-38", Decimal(0, 1), 1, 38); |
| 250 | + AssertDecimalFromString( |
| 251 | + "-1e-38", Decimal(18446744073709551615ULL, 18446744073709551615ULL), 1, 38); |
| 252 | + AssertDecimalFromString("9.99e-36", Decimal(0, 999), 3, 38); |
| 253 | + AssertDecimalFromString( |
| 254 | + "-9.99e-36", Decimal(18446744073709551615ULL, 18446744073709550617ULL), 3, 38); |
| 255 | + AssertDecimalFromString("987e-38", Decimal(0, 987), 3, 38); |
| 256 | + AssertDecimalFromString( |
| 257 | + "-987e-38", Decimal(18446744073709551615ULL, 18446744073709550629ULL), 3, 38); |
| 258 | + AssertDecimalFromString("99999999999999999999999999999999999999e-37", dec38times9pos, |
| 259 | + 38, 37); |
| 260 | + AssertDecimalFromString("-99999999999999999999999999999999999999e-37", dec38times9neg, |
| 261 | + 38, 37); |
| 262 | + AssertDecimalFromString("99999999999999999999999999999999999999e-38", dec38times9pos, |
| 263 | + 38, 38); |
| 264 | + AssertDecimalFromString("-99999999999999999999999999999999999999e-38", dec38times9neg, |
| 265 | + 38, 38); |
| 266 | +} |
| 267 | + |
| 268 | +TEST(DecimalTest, FromStringInvalid) { |
| 269 | + // Empty string |
| 270 | + auto result = Decimal::FromString(""); |
| 271 | + ASSERT_THAT(result, IsError(ErrorKind::kInvalidArgument)); |
| 272 | + ASSERT_THAT(result, HasErrorMessage( |
| 273 | + "Decimal::FromString: empty string is not a valid Decimal")); |
| 274 | + for (const auto& invalid_string : |
| 275 | + std::vector<std::string>{"-", "0.0.0", "0-13-32", "a", "-23092.235-", |
| 276 | + "-+23092.235", "+-23092.235", "00a", "1e1a", "0.00123D/3", |
| 277 | + "1.23eA8", "1.23E+3A", "-1.23E--5", "1.2345E+++07"}) { |
| 278 | + auto result = Decimal::FromString(invalid_string); |
| 279 | + ASSERT_THAT(result, IsError(ErrorKind::kInvalidArgument)); |
| 280 | + ASSERT_THAT(result, HasErrorMessage("Decimal::FromString: invalid decimal string")); |
| 281 | + } |
| 282 | + |
| 283 | + for (const auto& invalid_string : |
| 284 | + std::vector<std::string>{"1e39", "-1e39", "9e39", "-9e39", "9.9e40", "-9.9e40"}) { |
| 285 | + auto result = Decimal::FromString(invalid_string); |
| 286 | + ASSERT_THAT(result, IsError(ErrorKind::kInvalidArgument)); |
| 287 | + ASSERT_THAT(result, |
| 288 | + HasErrorMessage("Decimal::FromString: scale must be in the range")); |
| 289 | + } |
| 290 | +} |
| 291 | + |
| 292 | +TEST(DecimalTest, Division) { |
| 293 | + const std::string expected_string_value("-23923094039234029"); |
| 294 | + const Decimal value(expected_string_value); |
| 295 | + const Decimal result(value / 3); |
| 296 | + const Decimal expected_value("-7974364679744676"); |
| 297 | + ASSERT_EQ(expected_value, result); |
| 298 | +} |
| 299 | + |
| 300 | +TEST(DecimalTest, ToString) { |
| 301 | + struct ToStringCase { |
| 302 | + int64_t test_value; |
| 303 | + int32_t scale; |
| 304 | + const char* expected_string; |
| 305 | + }; |
| 306 | + |
| 307 | + for (const auto& t : std::vector<ToStringCase>{ |
| 308 | + {.test_value = 0, .scale = -1, .expected_string = "0E+1"}, |
| 309 | + {.test_value = 0, .scale = 0, .expected_string = "0"}, |
| 310 | + {.test_value = 0, .scale = 1, .expected_string = "0.0"}, |
| 311 | + {.test_value = 0, .scale = 6, .expected_string = "0.000000"}, |
| 312 | + {.test_value = 2, .scale = 7, .expected_string = "2E-7"}, |
| 313 | + {.test_value = 2, .scale = -1, .expected_string = "2E+1"}, |
| 314 | + {.test_value = 2, .scale = 0, .expected_string = "2"}, |
| 315 | + {.test_value = 2, .scale = 1, .expected_string = "0.2"}, |
| 316 | + {.test_value = 2, .scale = 6, .expected_string = "0.000002"}, |
| 317 | + {.test_value = -2, .scale = 7, .expected_string = "-2E-7"}, |
| 318 | + {.test_value = -2, .scale = 7, .expected_string = "-2E-7"}, |
| 319 | + {.test_value = -2, .scale = -1, .expected_string = "-2E+1"}, |
| 320 | + {.test_value = -2, .scale = 0, .expected_string = "-2"}, |
| 321 | + {.test_value = -2, .scale = 1, .expected_string = "-0.2"}, |
| 322 | + {.test_value = -2, .scale = 6, .expected_string = "-0.000002"}, |
| 323 | + {.test_value = -2, .scale = 7, .expected_string = "-2E-7"}, |
| 324 | + {.test_value = 123, .scale = -3, .expected_string = "1.23E+5"}, |
| 325 | + {.test_value = 123, .scale = -1, .expected_string = "1.23E+3"}, |
| 326 | + {.test_value = 123, .scale = 1, .expected_string = "12.3"}, |
| 327 | + {.test_value = 123, .scale = 0, .expected_string = "123"}, |
| 328 | + {.test_value = 123, .scale = 5, .expected_string = "0.00123"}, |
| 329 | + {.test_value = 123, .scale = 8, .expected_string = "0.00000123"}, |
| 330 | + {.test_value = 123, .scale = 9, .expected_string = "1.23E-7"}, |
| 331 | + {.test_value = 123, .scale = 10, .expected_string = "1.23E-8"}, |
| 332 | + {.test_value = -123, .scale = -3, .expected_string = "-1.23E+5"}, |
| 333 | + {.test_value = -123, .scale = -1, .expected_string = "-1.23E+3"}, |
| 334 | + {.test_value = -123, .scale = 1, .expected_string = "-12.3"}, |
| 335 | + {.test_value = -123, .scale = 0, .expected_string = "-123"}, |
| 336 | + {.test_value = -123, .scale = 5, .expected_string = "-0.00123"}, |
| 337 | + {.test_value = -123, .scale = 8, .expected_string = "-0.00000123"}, |
| 338 | + {.test_value = -123, .scale = 9, .expected_string = "-1.23E-7"}, |
| 339 | + {.test_value = -123, .scale = 10, .expected_string = "-1.23E-8"}, |
| 340 | + {.test_value = 1000000000, .scale = -3, .expected_string = "1.000000000E+12"}, |
| 341 | + {.test_value = 1000000000, .scale = -1, .expected_string = "1.000000000E+10"}, |
| 342 | + {.test_value = 1000000000, .scale = 0, .expected_string = "1000000000"}, |
| 343 | + {.test_value = 1000000000, .scale = 1, .expected_string = "100000000.0"}, |
| 344 | + {.test_value = 1000000000, .scale = 5, .expected_string = "10000.00000"}, |
| 345 | + {.test_value = 1000000000, |
| 346 | + .scale = 15, |
| 347 | + .expected_string = "0.000001000000000"}, |
| 348 | + {.test_value = 1000000000, .scale = 16, .expected_string = "1.000000000E-7"}, |
| 349 | + {.test_value = 1000000000, .scale = 17, .expected_string = "1.000000000E-8"}, |
| 350 | + {.test_value = -1000000000, |
| 351 | + .scale = -3, |
| 352 | + .expected_string = "-1.000000000E+12"}, |
| 353 | + {.test_value = -1000000000, |
| 354 | + .scale = -1, |
| 355 | + .expected_string = "-1.000000000E+10"}, |
| 356 | + {.test_value = -1000000000, .scale = 0, .expected_string = "-1000000000"}, |
| 357 | + {.test_value = -1000000000, .scale = 1, .expected_string = "-100000000.0"}, |
| 358 | + {.test_value = -1000000000, .scale = 5, .expected_string = "-10000.00000"}, |
| 359 | + {.test_value = -1000000000, |
| 360 | + .scale = 15, |
| 361 | + .expected_string = "-0.000001000000000"}, |
| 362 | + {.test_value = -1000000000, .scale = 16, .expected_string = "-1.000000000E-7"}, |
| 363 | + {.test_value = -1000000000, .scale = 17, .expected_string = "-1.000000000E-8"}, |
| 364 | + {.test_value = 1234567890123456789LL, |
| 365 | + .scale = -3, |
| 366 | + .expected_string = "1.234567890123456789E+21"}, |
| 367 | + {.test_value = 1234567890123456789LL, |
| 368 | + .scale = -1, |
| 369 | + .expected_string = "1.234567890123456789E+19"}, |
| 370 | + {.test_value = 1234567890123456789LL, |
| 371 | + .scale = 0, |
| 372 | + .expected_string = "1234567890123456789"}, |
| 373 | + {.test_value = 1234567890123456789LL, |
| 374 | + .scale = 1, |
| 375 | + .expected_string = "123456789012345678.9"}, |
| 376 | + {.test_value = 1234567890123456789LL, |
| 377 | + .scale = 5, |
| 378 | + .expected_string = "12345678901234.56789"}, |
| 379 | + {.test_value = 1234567890123456789LL, |
| 380 | + .scale = 24, |
| 381 | + .expected_string = "0.000001234567890123456789"}, |
| 382 | + {.test_value = 1234567890123456789LL, |
| 383 | + .scale = 25, |
| 384 | + .expected_string = "1.234567890123456789E-7"}, |
| 385 | + {.test_value = -1234567890123456789LL, |
| 386 | + .scale = -3, |
| 387 | + .expected_string = "-1.234567890123456789E+21"}, |
| 388 | + {.test_value = -1234567890123456789LL, |
| 389 | + .scale = -1, |
| 390 | + .expected_string = "-1.234567890123456789E+19"}, |
| 391 | + {.test_value = -1234567890123456789LL, |
| 392 | + .scale = 0, |
| 393 | + .expected_string = "-1234567890123456789"}, |
| 394 | + {.test_value = -1234567890123456789LL, |
| 395 | + .scale = 1, |
| 396 | + .expected_string = "-123456789012345678.9"}, |
| 397 | + {.test_value = -1234567890123456789LL, |
| 398 | + .scale = 5, |
| 399 | + .expected_string = "-12345678901234.56789"}, |
| 400 | + {.test_value = -1234567890123456789LL, |
| 401 | + .scale = 24, |
| 402 | + .expected_string = "-0.000001234567890123456789"}, |
| 403 | + {.test_value = -1234567890123456789LL, |
| 404 | + .scale = 25, |
| 405 | + .expected_string = "-1.234567890123456789E-7"}, |
| 406 | + }) { |
| 407 | + const Decimal value(t.test_value); |
| 408 | + auto result = value.ToString(t.scale); |
| 409 | + ASSERT_THAT(result, IsOk()) |
| 410 | + << "Failed to convert Decimal to string: " << value.ToIntegerString() |
| 411 | + << ", scale: " << t.scale; |
| 412 | + |
| 413 | + EXPECT_EQ(result.value(), t.expected_string) |
| 414 | + << "Expected: " << t.expected_string << ", but got: " << result.value(); |
| 415 | + } |
| 416 | +} |
| 417 | + |
167 | 418 | } // namespace iceberg |
0 commit comments