| # 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. |
| |
| import unittest |
| from decimal import Decimal as BigDecimal |
| |
| from pypaimon.data.decimal import Decimal |
| |
| |
| class DecimalTest(unittest.TestCase): |
| |
| def test_from_big_decimal(self): |
| d = Decimal.from_big_decimal(BigDecimal("12.34"), precision=10, scale=2) |
| |
| self.assertIsNotNone(d) |
| self.assertEqual(d.precision, 10) |
| self.assertEqual(d.scale, 2) |
| self.assertEqual(d.to_big_decimal(), BigDecimal("12.34")) |
| self.assertEqual(d.to_unscaled_long(), 1234) |
| |
| def test_from_big_decimal_overflow(self): |
| d = Decimal.from_big_decimal(BigDecimal("12345678901.23"), precision=10, scale=2) |
| self.assertIsNone(d) |
| |
| def test_from_unscaled_long(self): |
| d = Decimal.from_unscaled_long(1234, precision=10, scale=2) |
| |
| self.assertEqual(d.precision, 10) |
| self.assertEqual(d.scale, 2) |
| self.assertEqual(d.to_big_decimal(), BigDecimal("12.34")) |
| |
| def test_from_unscaled_bytes(self): |
| d = Decimal.from_big_decimal(BigDecimal("12.34"), 10, 2) |
| |
| d2 = Decimal.from_unscaled_bytes( |
| d.to_unscaled_bytes(), |
| precision=10, |
| scale=2, |
| ) |
| |
| self.assertEqual(d2, d) |
| |
| def test_zero(self): |
| d = Decimal.zero(precision=10, scale=2) |
| self.assertEqual(d.to_big_decimal(), BigDecimal("0.00")) |
| |
| def test_copy(self): |
| d = Decimal.from_big_decimal(BigDecimal("12.34"), 10, 2) |
| copy = d.copy() |
| |
| self.assertEqual(copy, d) |
| self.assertIsNot(copy, d) |
| |
| def test_compare(self): |
| d1 = Decimal.from_big_decimal(BigDecimal("1.23"), 10, 2) |
| d2 = Decimal.from_big_decimal(BigDecimal("2.34"), 10, 2) |
| |
| self.assertLess(d1.compare_to(d2), 0) |
| self.assertGreater(d2.compare_to(d1), 0) |
| self.assertEqual(d1.compare_to(d1.copy()), 0) |
| |
| def test_hash(self): |
| d1 = Decimal.from_big_decimal(BigDecimal("12.34"), 10, 2) |
| d2 = Decimal.from_big_decimal(BigDecimal("12.34"), 10, 2) |
| |
| self.assertEqual(hash(d1), hash(d2)) |
| |
| def test_to_big_decimal(self): |
| d = Decimal.from_unscaled_long(1234, 10, 2) |
| self.assertEqual(d.to_big_decimal(), BigDecimal("12.34")) |
| |
| def test_to_unscaled_long(self): |
| d = Decimal.from_big_decimal(BigDecimal("12.34"), 10, 2) |
| self.assertEqual(d.to_unscaled_long(), 1234) |
| |
| def test_to_unscaled_bytes(self): |
| d = Decimal.from_big_decimal(BigDecimal("12.34"), 10, 2) |
| |
| self.assertEqual( |
| int.from_bytes(d.to_unscaled_bytes(), "big", signed=True), |
| 1234, |
| ) |
| |
| def test_to_string(self): |
| self.assertEqual( |
| str(Decimal.from_big_decimal(BigDecimal("12.34"), 10, 2)), |
| "12.34", |
| ) |
| |
| self.assertEqual( |
| str(Decimal.from_big_decimal(BigDecimal("0.0000000000000000001"), 39, 19)), |
| "0.0000000000000000001", |
| ) |
| |
| def test_is_compact(self): |
| self.assertTrue(Decimal.is_compact_precision(18)) |
| self.assertFalse(Decimal.is_compact_precision(19)) |
| |
| self.assertTrue(Decimal.from_big_decimal(BigDecimal("1"), 18, 0).is_compact()) |
| self.assertFalse(Decimal.from_big_decimal(BigDecimal("1"), 19, 0).is_compact()) |
| |
| def test_extract_decimal_precision_scale(self): |
| self.assertEqual(Decimal.extract_decimal_precision_scale("DECIMAL"), (10, 0)) |
| self.assertEqual(Decimal.extract_decimal_precision_scale("DECIMAL(20)"), (20, 0)) |
| self.assertEqual(Decimal.extract_decimal_precision_scale("DECIMAL(20,5)"), (20, 5)) |
| self.assertEqual(Decimal.extract_decimal_precision_scale("NUMERIC(18,2)"), (18, 2)) |
| self.assertEqual(Decimal.extract_decimal_precision_scale("DEC(8,3)"), (8, 3)) |
| |
| def test_extract_decimal_precision_scale_invalid(self): |
| with self.assertRaises(ValueError): |
| Decimal.extract_decimal_precision_scale("INT") |
| |
| def test_high_precision_unscaled_bytes_round_trip(self): |
| value = BigDecimal("12345678901234567890123456789012345678") |
| |
| d = Decimal.from_big_decimal(value, precision=38, scale=0) |
| |
| self.assertIsNotNone(d) |
| |
| encoded = d.to_unscaled_bytes() |
| |
| d2 = Decimal.from_unscaled_bytes( |
| encoded, |
| precision=38, |
| scale=0, |
| ) |
| |
| self.assertEqual(d2, d) |
| self.assertEqual(d2.to_big_decimal(), value) |
| |
| def test_high_precision_scaled_round_trip(self): |
| value = BigDecimal("12345678901234567890.123456789012345678") |
| |
| d = Decimal.from_big_decimal( |
| value, |
| precision=38, |
| scale=18, |
| ) |
| |
| self.assertIsNotNone(d) |
| |
| d2 = Decimal.from_unscaled_bytes( |
| d.to_unscaled_bytes(), |
| precision=38, |
| scale=18, |
| ) |
| |
| self.assertEqual(d2.to_big_decimal(), value) |
| |
| def test_to_unscaled_bytes_java_compatible(self): |
| cases = [ |
| (-32769, "ff7fff"), |
| (-32768, "8000"), |
| (-129, "ff7f"), |
| (-128, "80"), |
| (-127, "81"), |
| (-2, "fe"), |
| (-1, "ff"), |
| (0, "00"), |
| (1, "01"), |
| (127, "7f"), |
| (128, "0080"), |
| (255, "00ff"), |
| (256, "0100"), |
| (32767, "7fff"), |
| (32768, "008000"), |
| ] |
| |
| for unscaled, expected in cases: |
| with self.subTest(unscaled=unscaled): |
| d = Decimal.from_unscaled_long( |
| unscaled, |
| precision=18, |
| scale=0, |
| ) |
| |
| self.assertEqual( |
| d.to_unscaled_bytes().hex(), |
| expected, |
| ) |
| |
| def test_from_unscaled_bytes_java_compatible(self): |
| cases = [ |
| ("80", -128), |
| ("ff7f", -129), |
| ("ff", -1), |
| ("00", 0), |
| ("7f", 127), |
| ("0080", 128), |
| ("00ff", 255), |
| ] |
| |
| for encoded, expected in cases: |
| with self.subTest(encoded=encoded): |
| d = Decimal.from_unscaled_bytes( |
| bytes.fromhex(encoded), |
| precision=18, |
| scale=0, |
| ) |
| |
| self.assertEqual( |
| d.to_unscaled_long(), |
| expected, |
| ) |
| |
| def test_unscaled_bytes_round_trip(self): |
| values = [ |
| -123456789012345678, |
| -1000, |
| -129, |
| -128, |
| -127, |
| -1, |
| 0, |
| 1, |
| 127, |
| 128, |
| 255, |
| 256, |
| 123456789012345678, |
| ] |
| |
| for value in values: |
| with self.subTest(value=value): |
| d = Decimal.from_unscaled_long( |
| value, |
| precision=18, |
| scale=0, |
| ) |
| |
| d2 = Decimal.from_unscaled_bytes( |
| d.to_unscaled_bytes(), |
| precision=18, |
| scale=0, |
| ) |
| |
| self.assertEqual(d2, d) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |