1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#include <algorithm>
19#include <array>
20#include <cmath>
21#include <cstdint>
22#include <string>
23#include <tuple>
24
25#include <gtest/gtest.h>
26
27#include "arrow/status.h"
28#include "arrow/test-util.h"
29#include "arrow/util/decimal.h"
30#include "arrow/util/macros.h"
31
32namespace arrow {
33
34class DecimalTestFixture : public ::testing::Test {
35 public:
36 DecimalTestFixture() : integer_value_(23423445), string_value_("234.23445") {}
37 Decimal128 integer_value_;
38 std::string string_value_;
39};
40
41TEST_F(DecimalTestFixture, TestToString) {
42 Decimal128 decimal(this->integer_value_);
43 int32_t scale = 5;
44 std::string result = decimal.ToString(scale);
45 ASSERT_EQ(result, this->string_value_);
46}
47
48TEST_F(DecimalTestFixture, TestFromString) {
49 Decimal128 expected(this->integer_value_);
50 Decimal128 result;
51 int32_t precision, scale;
52 ASSERT_OK(Decimal128::FromString(this->string_value_, &result, &precision, &scale));
53 ASSERT_EQ(result, expected);
54 ASSERT_EQ(precision, 8);
55 ASSERT_EQ(scale, 5);
56}
57
58TEST_F(DecimalTestFixture, TestStringStartingWithPlus) {
59 std::string plus_value("+234.234");
60 Decimal128 out;
61 int32_t scale;
62 int32_t precision;
63 ASSERT_OK(Decimal128::FromString(plus_value, &out, &precision, &scale));
64 ASSERT_EQ(234234, out);
65 ASSERT_EQ(6, precision);
66 ASSERT_EQ(3, scale);
67}
68
69TEST_F(DecimalTestFixture, TestStringStartingWithPlus128) {
70 std::string plus_value("+2342394230592.232349023094");
71 Decimal128 expected_value("2342394230592232349023094");
72 Decimal128 out;
73 int32_t scale;
74 int32_t precision;
75 ASSERT_OK(Decimal128::FromString(plus_value, &out, &precision, &scale));
76 ASSERT_EQ(expected_value, out);
77 ASSERT_EQ(25, precision);
78 ASSERT_EQ(12, scale);
79}
80
81TEST(DecimalTest, TestFromStringDecimal128) {
82 std::string string_value("-23049223942343532412");
83 Decimal128 result(string_value);
84 Decimal128 expected(static_cast<int64_t>(-230492239423435324));
85 ASSERT_EQ(result, expected * 100 - 12);
86
87 // Sanity check that our number is actually using more than 64 bits
88 ASSERT_NE(result.high_bits(), 0);
89}
90
91TEST(DecimalTest, TestFromDecimalString128) {
92 std::string string_value("-23049223942343.532412");
93 Decimal128 result;
94 ASSERT_OK(Decimal128::FromString(string_value, &result));
95 Decimal128 expected(static_cast<int64_t>(-230492239423435324));
96 ASSERT_EQ(result, expected * 100 - 12);
97
98 // Sanity check that our number is actually using more than 64 bits
99 ASSERT_NE(result.high_bits(), 0);
100}
101
102TEST(DecimalTest, TestDecimal32SignedRoundTrip) {
103 Decimal128 expected("-3402692");
104
105 auto bytes = expected.ToBytes();
106 Decimal128 result(bytes.data());
107 ASSERT_EQ(expected, result);
108}
109
110TEST(DecimalTest, TestDecimal64SignedRoundTrip) {
111 Decimal128 expected;
112 std::string string_value("-34034293045.921");
113 ASSERT_OK(Decimal128::FromString(string_value, &expected));
114
115 auto bytes = expected.ToBytes();
116 Decimal128 result(bytes.data());
117
118 ASSERT_EQ(expected, result);
119}
120
121TEST(DecimalTest, TestDecimalStringAndBytesRoundTrip) {
122 Decimal128 expected;
123 std::string string_value("-340282366920938463463374607431.711455");
124 ASSERT_OK(Decimal128::FromString(string_value, &expected));
125
126 std::string expected_string_value("-340282366920938463463374607431711455");
127 Decimal128 expected_underlying_value(expected_string_value);
128
129 ASSERT_EQ(expected, expected_underlying_value);
130
131 auto bytes = expected.ToBytes();
132
133 Decimal128 result(bytes.data());
134
135 ASSERT_EQ(expected, result);
136}
137
138TEST(DecimalTest, TestInvalidInputMinus) {
139 std::string invalid_value("-");
140 Decimal128 out;
141 Status status = Decimal128::FromString(invalid_value, &out);
142 ASSERT_RAISES(Invalid, status);
143}
144
145TEST(DecimalTest, TestInvalidInputDot) {
146 std::string invalid_value("0.0.0");
147 Decimal128 out;
148 Status status = Decimal128::FromString(invalid_value, &out);
149 ASSERT_RAISES(Invalid, status);
150}
151
152TEST(DecimalTest, TestInvalidInputEmbeddedMinus) {
153 std::string invalid_value("0-13-32");
154 Decimal128 out;
155 Status status = Decimal128::FromString(invalid_value, &out);
156 ASSERT_RAISES(Invalid, status);
157}
158
159TEST(DecimalTest, TestInvalidInputSingleChar) {
160 std::string invalid_value("a");
161 Decimal128 out;
162 Status status = Decimal128::FromString(invalid_value, &out);
163 ASSERT_RAISES(Invalid, status);
164}
165
166TEST(DecimalTest, TestInvalidInputWithValidSubstring) {
167 std::string invalid_value("-23092.235-");
168 Decimal128 out;
169 Status status = Decimal128::FromString(invalid_value, &out);
170 auto msg = status.message();
171 ASSERT_RAISES(Invalid, status);
172}
173
174TEST(DecimalTest, TestInvalidInputWithMinusPlus) {
175 std::string invalid_value("-+23092.235");
176 Decimal128 out;
177 Status status = Decimal128::FromString(invalid_value, &out);
178 ASSERT_RAISES(Invalid, status);
179}
180
181TEST(DecimalTest, TestInvalidInputWithPlusMinus) {
182 std::string invalid_value("+-23092.235");
183 Decimal128 out;
184 Status status = Decimal128::FromString(invalid_value, &out);
185 ASSERT_RAISES(Invalid, status);
186}
187
188TEST(DecimalTest, TestInvalidInputWithLeadingZeros) {
189 std::string invalid_value("00a");
190 Decimal128 out;
191 Status status = Decimal128::FromString(invalid_value, &out);
192 ASSERT_RAISES(Invalid, status);
193}
194
195TEST(DecimalZerosTest, LeadingZerosNoDecimalPoint) {
196 std::string string_value("0000000");
197 Decimal128 d;
198 int32_t precision;
199 int32_t scale;
200 ASSERT_OK(Decimal128::FromString(string_value, &d, &precision, &scale));
201 ASSERT_EQ(0, precision);
202 ASSERT_EQ(0, scale);
203 ASSERT_EQ(0, d);
204}
205
206TEST(DecimalZerosTest, LeadingZerosDecimalPoint) {
207 std::string string_value("000.0000");
208 Decimal128 d;
209 int32_t precision;
210 int32_t scale;
211 ASSERT_OK(Decimal128::FromString(string_value, &d, &precision, &scale));
212 ASSERT_EQ(4, precision);
213 ASSERT_EQ(4, scale);
214 ASSERT_EQ(0, d);
215}
216
217TEST(DecimalZerosTest, NoLeadingZerosDecimalPoint) {
218 std::string string_value(".00000");
219 Decimal128 d;
220 int32_t precision;
221 int32_t scale;
222 ASSERT_OK(Decimal128::FromString(string_value, &d, &precision, &scale));
223 ASSERT_EQ(5, precision);
224 ASSERT_EQ(5, scale);
225 ASSERT_EQ(0, d);
226}
227
228template <typename T>
229class Decimal128Test : public ::testing::Test {
230 public:
231 Decimal128Test() : value_(42) {}
232 const T value_;
233};
234
235using Decimal128Types =
236 ::testing::Types<char, unsigned char, short, unsigned short, // NOLINT
237 int, unsigned int, long, unsigned long, // NOLINT
238 long long, unsigned long long // NOLINT
239 >;
240
241TYPED_TEST_CASE(Decimal128Test, Decimal128Types);
242
243TYPED_TEST(Decimal128Test, ConstructibleFromAnyIntegerType) {
244 Decimal128 value(this->value_);
245 ASSERT_EQ(42, value.low_bits());
246}
247
248TEST(Decimal128TestTrue, ConstructibleFromBool) {
249 Decimal128 value(true);
250 ASSERT_EQ(1, value.low_bits());
251}
252
253TEST(Decimal128TestFalse, ConstructibleFromBool) {
254 Decimal128 value(false);
255 ASSERT_EQ(0, value.low_bits());
256}
257
258TEST(Decimal128Test, Division) {
259 const std::string expected_string_value("-23923094039234029");
260 const Decimal128 value(expected_string_value);
261 const Decimal128 result(value / 3);
262 const Decimal128 expected_value("-7974364679744676");
263 ASSERT_EQ(expected_value, result);
264}
265
266TEST(Decimal128Test, PrintLargePositiveValue) {
267 const std::string string_value("99999999999999999999999999999999999999");
268 const Decimal128 value(string_value);
269 const std::string printed_value = value.ToIntegerString();
270 ASSERT_EQ(string_value, printed_value);
271}
272
273TEST(Decimal128Test, PrintLargeNegativeValue) {
274 const std::string string_value("-99999999999999999999999999999999999999");
275 const Decimal128 value(string_value);
276 const std::string printed_value = value.ToIntegerString();
277 ASSERT_EQ(string_value, printed_value);
278}
279
280TEST(Decimal128Test, PrintMaxValue) {
281 const std::string string_value("170141183460469231731687303715884105727");
282 const Decimal128 value(string_value);
283 const std::string printed_value = value.ToIntegerString();
284 ASSERT_EQ(string_value, printed_value);
285}
286
287TEST(Decimal128Test, PrintMinValue) {
288 const std::string string_value("-170141183460469231731687303715884105728");
289 const Decimal128 value(string_value);
290 const std::string printed_value = value.ToIntegerString();
291 ASSERT_EQ(string_value, printed_value);
292}
293
294class Decimal128PrintingTest
295 : public ::testing::TestWithParam<std::tuple<int32_t, int32_t, std::string>> {};
296
297TEST_P(Decimal128PrintingTest, Print) {
298 int32_t test_value;
299 int32_t scale;
300 std::string expected_string;
301 std::tie(test_value, scale, expected_string) = GetParam();
302 const Decimal128 value(test_value);
303 const std::string printed_value = value.ToString(scale);
304 ASSERT_EQ(expected_string, printed_value);
305}
306
307INSTANTIATE_TEST_CASE_P(Decimal128PrintingTest, Decimal128PrintingTest,
308 ::testing::Values(std::make_tuple(123, 1, "12.3"),
309 std::make_tuple(123, 5, "0.00123"),
310 std::make_tuple(123, 10, "1.23E-8"),
311 std::make_tuple(123, -1, "1.23E+3"),
312 std::make_tuple(-123, -1, "-1.23E+3"),
313 std::make_tuple(123, -3, "1.23E+5"),
314 std::make_tuple(-123, -3, "-1.23E+5"),
315 std::make_tuple(12345, -3, "1.2345E+7")));
316
317class Decimal128ParsingTest
318 : public ::testing::TestWithParam<std::tuple<std::string, uint64_t, int32_t>> {};
319
320TEST_P(Decimal128ParsingTest, Parse) {
321 std::string test_string;
322 uint64_t expected_low_bits;
323 int32_t expected_scale;
324 std::tie(test_string, expected_low_bits, expected_scale) = GetParam();
325 Decimal128 value;
326 int32_t scale;
327 ASSERT_OK(Decimal128::FromString(test_string, &value, NULLPTR, &scale));
328 ASSERT_EQ(value.low_bits(), expected_low_bits);
329 ASSERT_EQ(expected_scale, scale);
330}
331
332INSTANTIATE_TEST_CASE_P(Decimal128ParsingTest, Decimal128ParsingTest,
333 ::testing::Values(std::make_tuple("12.3", 123ULL, 1),
334 std::make_tuple("0.00123", 123ULL, 5),
335 std::make_tuple("1.23E-8", 123ULL, 10),
336 std::make_tuple("-1.23E-8", -123LL, 10),
337 std::make_tuple("1.23E+3", 1230ULL, 0),
338 std::make_tuple("-1.23E+3", -1230LL, 0),
339 std::make_tuple("1.23E+5", 123000ULL, 0),
340 std::make_tuple("1.2345E+7", 12345000ULL, 0),
341 std::make_tuple("1.23e-8", 123ULL, 10),
342 std::make_tuple("-1.23e-8", -123LL, 10),
343 std::make_tuple("1.23e+3", 1230ULL, 0),
344 std::make_tuple("-1.23e+3", -1230LL, 0),
345 std::make_tuple("1.23e+5", 123000ULL, 0),
346 std::make_tuple("1.2345e+7", 12345000ULL, 0)));
347
348class Decimal128ParsingTestInvalid : public ::testing::TestWithParam<std::string> {};
349
350TEST_P(Decimal128ParsingTestInvalid, Parse) {
351 std::string test_string = GetParam();
352 Decimal128 value;
353 ASSERT_RAISES(Invalid, Decimal128::FromString(test_string, &value));
354}
355
356INSTANTIATE_TEST_CASE_P(Decimal128ParsingTestInvalid, Decimal128ParsingTestInvalid,
357 ::testing::Values("0.00123D/3", "1.23eA8", "1.23E+3A",
358 "-1.23E--5", "1.2345E+++07"));
359
360TEST(Decimal128ParseTest, WithExponentAndNullptrScale) {
361 Decimal128 value;
362 ASSERT_OK(Decimal128::FromString("1.23E-8", &value));
363
364 const Decimal128 expected_value(123);
365 ASSERT_EQ(expected_value, value);
366}
367
368TEST(Decimal128Test, TestSmallNumberFormat) {
369 Decimal128 value("0.2");
370 std::string expected("0.2");
371
372 const int32_t scale = 1;
373 std::string result = value.ToString(scale);
374 ASSERT_EQ(expected, result);
375}
376
377TEST(Decimal128Test, TestNoDecimalPointExponential) {
378 Decimal128 value;
379 int32_t precision;
380 int32_t scale;
381 ASSERT_OK(Decimal128::FromString("1E1", &value, &precision, &scale));
382 ASSERT_EQ(10, value.low_bits());
383 ASSERT_EQ(2, precision);
384 ASSERT_EQ(0, scale);
385}
386
387TEST(Decimal128Test, TestFromBigEndian) {
388 // We test out a variety of scenarios:
389 //
390 // * Positive values that are left shifted
391 // and filled in with the same bit pattern
392 // * Negated of the positive values
393 // * Complement of the positive values
394 //
395 // For the positive values, we can call FromBigEndian
396 // with a length that is less than 16, whereas we must
397 // pass all 16 bytes for the negative and complement.
398 //
399 // We use a number of bit patterns to increase the coverage
400 // of scenarios
401 for (int32_t start : {1, 15, /* 00001111 */
402 85, /* 01010101 */
403 127 /* 01111111 */}) {
404 Decimal128 value(start);
405 for (int ii = 0; ii < 16; ++ii) {
406 auto little_endian = value.ToBytes();
407 std::reverse(little_endian.begin(), little_endian.end());
408 Decimal128 out;
409 // Limit the number of bytes we are passing to make
410 // sure that it works correctly. That's why all of the
411 // 'start' values don't have a 1 in the most significant
412 // bit place
413 ASSERT_OK(Decimal128::FromBigEndian(little_endian.data() + 15 - ii, ii + 1, &out));
414 ASSERT_EQ(value, out);
415
416 // Negate it and convert to big endian
417 auto negated = -value;
418 little_endian = negated.ToBytes();
419 std::reverse(little_endian.begin(), little_endian.end());
420 // The sign bit is looked up in the MSB
421 ASSERT_OK(Decimal128::FromBigEndian(little_endian.data() + 15 - ii, ii + 1, &out));
422 ASSERT_EQ(negated, out);
423
424 // Take the complement and convert to big endian
425 auto complement = ~value;
426 little_endian = complement.ToBytes();
427 std::reverse(little_endian.begin(), little_endian.end());
428 ASSERT_OK(Decimal128::FromBigEndian(little_endian.data(), 16, &out));
429 ASSERT_EQ(complement, out);
430
431 value <<= 8;
432 value += Decimal128(start);
433 }
434 }
435}
436
437TEST(Decimal128Test, TestFromBigEndianBadLength) {
438 Decimal128 out;
439 ASSERT_RAISES(Invalid, Decimal128::FromBigEndian(0, -1, &out));
440 ASSERT_RAISES(Invalid, Decimal128::FromBigEndian(0, 17, &out));
441}
442
443TEST(Decimal128Test, TestToInteger) {
444 Decimal128 value1("1234");
445 int32_t out1;
446
447 Decimal128 value2("-1234");
448 int64_t out2;
449
450 ASSERT_OK(value1.ToInteger(&out1));
451 ASSERT_EQ(1234, out1);
452
453 ASSERT_OK(value1.ToInteger(&out2));
454 ASSERT_EQ(1234, out2);
455
456 ASSERT_OK(value2.ToInteger(&out1));
457 ASSERT_EQ(-1234, out1);
458
459 ASSERT_OK(value2.ToInteger(&out2));
460 ASSERT_EQ(-1234, out2);
461
462 Decimal128 invalid_int32(static_cast<int64_t>(std::pow(2, 31)));
463 ASSERT_RAISES(Invalid, invalid_int32.ToInteger(&out1));
464
465 Decimal128 invalid_int64("12345678912345678901");
466 ASSERT_RAISES(Invalid, invalid_int64.ToInteger(&out2));
467}
468
469TEST(Decimal128Test, GetWholeAndFraction) {
470 Decimal128 value("123456");
471 Decimal128 whole;
472 Decimal128 fraction;
473 int32_t out;
474
475 value.GetWholeAndFraction(0, &whole, &fraction);
476 ASSERT_OK(whole.ToInteger(&out));
477 ASSERT_EQ(123456, out);
478 ASSERT_OK(fraction.ToInteger(&out));
479 ASSERT_EQ(0, out);
480
481 value.GetWholeAndFraction(1, &whole, &fraction);
482 ASSERT_OK(whole.ToInteger(&out));
483 ASSERT_EQ(12345, out);
484 ASSERT_OK(fraction.ToInteger(&out));
485 ASSERT_EQ(6, out);
486
487 value.GetWholeAndFraction(5, &whole, &fraction);
488 ASSERT_OK(whole.ToInteger(&out));
489 ASSERT_EQ(1, out);
490 ASSERT_OK(fraction.ToInteger(&out));
491 ASSERT_EQ(23456, out);
492
493 value.GetWholeAndFraction(7, &whole, &fraction);
494 ASSERT_OK(whole.ToInteger(&out));
495 ASSERT_EQ(0, out);
496 ASSERT_OK(fraction.ToInteger(&out));
497 ASSERT_EQ(123456, out);
498}
499
500TEST(Decimal128Test, GetWholeAndFractionNegative) {
501 Decimal128 value("-123456");
502 Decimal128 whole;
503 Decimal128 fraction;
504 int32_t out;
505
506 value.GetWholeAndFraction(0, &whole, &fraction);
507 ASSERT_OK(whole.ToInteger(&out));
508 ASSERT_EQ(-123456, out);
509 ASSERT_OK(fraction.ToInteger(&out));
510 ASSERT_EQ(0, out);
511
512 value.GetWholeAndFraction(1, &whole, &fraction);
513 ASSERT_OK(whole.ToInteger(&out));
514 ASSERT_EQ(-12345, out);
515 ASSERT_OK(fraction.ToInteger(&out));
516 ASSERT_EQ(-6, out);
517
518 value.GetWholeAndFraction(5, &whole, &fraction);
519 ASSERT_OK(whole.ToInteger(&out));
520 ASSERT_EQ(-1, out);
521 ASSERT_OK(fraction.ToInteger(&out));
522 ASSERT_EQ(-23456, out);
523
524 value.GetWholeAndFraction(7, &whole, &fraction);
525 ASSERT_OK(whole.ToInteger(&out));
526 ASSERT_EQ(0, out);
527 ASSERT_OK(fraction.ToInteger(&out));
528 ASSERT_EQ(-123456, out);
529}
530
531TEST(Decimal128Test, IncreaseScale) {
532 Decimal128 result;
533 int32_t out;
534
535 result = Decimal128("1234").IncreaseScaleBy(3);
536 ASSERT_OK(result.ToInteger(&out));
537 ASSERT_EQ(1234000, out);
538
539 result = Decimal128("-1234").IncreaseScaleBy(3);
540 ASSERT_OK(result.ToInteger(&out));
541 ASSERT_EQ(-1234000, out);
542}
543
544TEST(Decimal128Test, ReduceScaleAndRound) {
545 Decimal128 result;
546 int32_t out;
547
548 result = Decimal128("123456").ReduceScaleBy(1, false);
549 ASSERT_OK(result.ToInteger(&out));
550 ASSERT_EQ(12345, out);
551
552 result = Decimal128("123456").ReduceScaleBy(1, true);
553 ASSERT_OK(result.ToInteger(&out));
554 ASSERT_EQ(12346, out);
555
556 result = Decimal128("123451").ReduceScaleBy(1, true);
557 ASSERT_OK(result.ToInteger(&out));
558 ASSERT_EQ(12345, out);
559
560 result = Decimal128("-123789").ReduceScaleBy(2, true);
561 ASSERT_OK(result.ToInteger(&out));
562 ASSERT_EQ(-1238, out);
563
564 result = Decimal128("-123749").ReduceScaleBy(2, true);
565 ASSERT_OK(result.ToInteger(&out));
566 ASSERT_EQ(-1237, out);
567
568 result = Decimal128("-123750").ReduceScaleBy(2, true);
569 ASSERT_OK(result.ToInteger(&out));
570 ASSERT_EQ(-1238, out);
571}
572
573} // namespace arrow
574