| 1 | // Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | import math.big |
| 5 | import math.decimal |
| 6 | |
| 7 | fn must_decimal(value string) decimal.Decimal { |
| 8 | return decimal.from_string(value) or { panic(err) } |
| 9 | } |
| 10 | |
| 11 | fn assert_parse_error(value string, expected string) { |
| 12 | decimal.from_string(value) or { |
| 13 | assert err.msg() == expected |
| 14 | return |
| 15 | } |
| 16 | assert false |
| 17 | } |
| 18 | |
| 19 | fn assert_div_error(dividend string, divisor string, precision int, expected string) { |
| 20 | must_decimal(dividend).div_prec(must_decimal(divisor), precision) or { |
| 21 | assert err.msg() == expected |
| 22 | return |
| 23 | } |
| 24 | assert false |
| 25 | } |
| 26 | |
| 27 | fn test_new_and_getters() { |
| 28 | value := decimal.new(big.integer_from_string('123400') or { panic(err) }, 4) |
| 29 | assert value.str() == '12.34' |
| 30 | assert value.coefficient().str() == '1234' |
| 31 | assert value.scale() == 2 |
| 32 | } |
| 33 | |
| 34 | fn test_from_ints() { |
| 35 | assert decimal.from_int(42).str() == '42' |
| 36 | assert decimal.from_i64(-99).str() == '-99' |
| 37 | assert decimal.from_u64(123456789).str() == '123456789' |
| 38 | } |
| 39 | |
| 40 | fn test_from_string_normalizes_trailing_zeroes() { |
| 41 | assert must_decimal('00123.4500').str() == '123.45' |
| 42 | assert must_decimal('-0.5000').str() == '-0.5' |
| 43 | assert must_decimal('.25').str() == '0.25' |
| 44 | assert must_decimal('5.').str() == '5' |
| 45 | assert must_decimal('0.000').is_zero() |
| 46 | } |
| 47 | |
| 48 | fn test_from_string_rejects_invalid_input() { |
| 49 | assert_parse_error('', 'math.decimal: empty input') |
| 50 | assert_parse_error('abc', 'math.decimal: invalid decimal value `abc`') |
| 51 | assert_parse_error('1.2.3', 'math.decimal: invalid decimal value `1.2.3`') |
| 52 | assert_parse_error('+-1', 'math.decimal: invalid decimal value `+-1`') |
| 53 | assert_parse_error('.', 'math.decimal: invalid decimal value `.`') |
| 54 | } |
| 55 | |
| 56 | fn test_add_sub_and_mul() { |
| 57 | assert (must_decimal('1.23') + must_decimal('4.5')).str() == '5.73' |
| 58 | assert (must_decimal('4.5') - must_decimal('1.23')).str() == '3.27' |
| 59 | assert (must_decimal('12.5') * must_decimal('0.04')).str() == '0.5' |
| 60 | assert (must_decimal('9999999999999999999999999999.99') + must_decimal('0.01')).str() == '10000000000000000000000000000' |
| 61 | } |
| 62 | |
| 63 | fn test_neg_abs_and_compare() { |
| 64 | assert must_decimal('-1.20').neg().str() == '1.2' |
| 65 | assert must_decimal('-1.20').abs().str() == '1.2' |
| 66 | assert must_decimal('1.20') == must_decimal('1.2') |
| 67 | assert must_decimal('-2') < must_decimal('-1.99') |
| 68 | assert must_decimal('0.009') < must_decimal('0.01') |
| 69 | } |
| 70 | |
| 71 | fn test_div_prec_rounds_half_up() { |
| 72 | assert must_decimal('1').div_prec(must_decimal('3'), 2)!.str() == '0.33' |
| 73 | assert must_decimal('1').div_prec(must_decimal('6'), 2)!.str() == '0.17' |
| 74 | assert must_decimal('-1').div_prec(must_decimal('6'), 2)!.str() == '-0.17' |
| 75 | assert must_decimal('10').div_prec(must_decimal('4'), 2)!.str() == '2.5' |
| 76 | } |
| 77 | |
| 78 | fn test_div_operator_uses_default_precision() { |
| 79 | assert (must_decimal('1') / must_decimal('8')).str() == '0.125' |
| 80 | assert (must_decimal('1') / must_decimal('3')).str() == '0.3333333333333333' |
| 81 | } |
| 82 | |
| 83 | fn test_div_prec_errors() { |
| 84 | assert_div_error('1', '0', 2, 'math.decimal: cannot divide by zero') |
| 85 | assert_div_error('1', '2', -1, 'math.decimal: precision cannot be negative') |
| 86 | } |
| 87 | |