package math import ( "fmt" "math" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewDecFromString(t *testing.T) { specs := map[string]struct { src string exp Dec expErr error }{ "simple decimal": { src: "1", exp: NewDecFromInt64(1), }, "simple negative decimal": { src: "-1", exp: NewDecFromInt64(-1), }, "valid decimal with decimal places": { src: "1.234", exp: NewDecWithExp(1234, -3), }, "valid negative decimal": { src: "-1.234", exp: NewDecWithExp(-1234, -3), }, "min decimal": { src: "-" + strings.Repeat("9", 34), exp: must(NewDecWithExp(-1, 34).Add(NewDecFromInt64(1))), }, "max decimal": { src: strings.Repeat("9", 34), exp: must(NewDecWithExp(1, 34).Sub(NewDecFromInt64(1))), }, "too big": { src: strings.Repeat("9", 100_0000), expErr: ErrInvalidDec, }, "too small": { src: "-" + strings.Repeat("9", 100_0000), expErr: ErrInvalidDec, }, "valid decimal with leading zero": { src: "01234", exp: NewDecWithExp(1234, 0), }, "valid decimal without leading zero": { src: ".1234", exp: NewDecWithExp(1234, -4), }, "valid decimal without trailing digits": { src: "123.", exp: NewDecWithExp(123, 0), }, "valid negative decimal without leading zero": { src: "-.1234", exp: NewDecWithExp(-1234, -4), }, "valid negative decimal without trailing digits": { src: "-123.", exp: NewDecWithExp(-123, 0), }, "decimal with scientific notation": { src: "1.23e4", exp: NewDecWithExp(123, 2), }, "decimal with upper case scientific notation": { src: "1.23E+4", exp: NewDecWithExp(123, 2), }, "negative decimal with scientific notation": { src: "-1.23e4", exp: NewDecWithExp(-123, 2), }, "exceed max exp 11E+1000000": { src: "11E+1000000", expErr: ErrInvalidDec, }, "exceed min exp 11E-1000000": { src: "11E-1000000", expErr: ErrInvalidDec, }, "exceed max exp 1E100001": { src: "1E100001", expErr: ErrInvalidDec, }, "exceed min exp 1E-100001": { src: "1E-100001", expErr: ErrInvalidDec, }, "empty string": { src: "", expErr: ErrInvalidDec, }, "NaN": { src: "NaN", expErr: ErrInvalidDec, }, "random string": { src: "1foo", expErr: ErrInvalidDec, }, "Infinity": { src: "Infinity", expErr: ErrInvalidDec, }, "Inf": { src: "Inf", expErr: ErrInvalidDec, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got, gotErr := NewDecFromString(spec.src) if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr, got.String()) return } require.NoError(t, gotErr) assert.True(t, spec.exp.Equal(got)) }) } } func TestNewDecFromInt64(t *testing.T) { specs := map[string]struct { src int64 exp string }{ "zero value": { src: 0, exp: "0", }, "positive value": { src: 123, exp: "123", }, "negative value": { src: -123, exp: "-123", }, "max value": { src: math.MaxInt64, exp: "9223372036854.775807E+6", }, "min value": { src: math.MinInt64, exp: "9223372036854.775808E+6", }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got := NewDecFromInt64(spec.src) assert.Equal(t, spec.exp, got.String()) }) } } func TestAdd(t *testing.T) { specs := map[string]struct { x Dec y Dec exp Dec expErr error }{ "0 + 0 = 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), exp: NewDecFromInt64(0), }, "0 + 123 = 123": { x: NewDecFromInt64(0), y: NewDecFromInt64(123), exp: NewDecFromInt64(123), }, "0 + -123 = -123": { x: NewDecFromInt64(0), y: NewDecFromInt64(-123), exp: NewDecFromInt64(-123), }, "123 + 123 = 246": { x: NewDecFromInt64(123), y: NewDecFromInt64(123), exp: NewDecFromInt64(246), }, "-123 + 123 = 0": { x: NewDecFromInt64(-123), y: NewDecFromInt64(123), exp: NewDecFromInt64(0), }, "-123 + -123 = -246": { x: NewDecFromInt64(-123), y: NewDecFromInt64(-123), exp: NewDecFromInt64(-246), }, "1.234 + 1.234 = 2.468": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(1234, -3), exp: NewDecWithExp(2468, -3), }, "1.234 + 123 = 124.234": { x: NewDecWithExp(1234, -3), y: NewDecFromInt64(123), exp: NewDecWithExp(124234, -3), }, "1.234 + -123 = -121.766": { x: NewDecWithExp(1234, -3), y: NewDecFromInt64(-123), exp: must(NewDecFromString("-121.766")), }, "1.234 + -1.234 = 0": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(-1234, -3), exp: NewDecWithExp(0, -3), }, "-1.234 + -1.234 = -2.468": { x: NewDecWithExp(-1234, -3), y: NewDecWithExp(-1234, -3), exp: NewDecWithExp(-2468, -3), }, "1e100000 + 9e900000 -> Err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(9, 900_000), expErr: ErrInvalidDec, }, "1e100000 + -9e900000 -> Err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(9, 900_000), expErr: ErrInvalidDec, }, "1e100000 + 1e^-1 -> err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(1, -1), expErr: ErrInvalidDec, }, "1e100000 + -1e^-1 -> err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(-1, -1), expErr: ErrInvalidDec, }, "1e100000 + 1 -> 100..1": { x: NewDecWithExp(1, 100_000), y: NewDecFromInt64(1), exp: must(NewDecWithExp(1, 100_000).Add(NewDecFromInt64(1))), }, "1e100001 + 0 -> err": { x: NewDecWithExp(1, 100_001), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "-1e100001 + 0 -> err": { x: NewDecWithExp(1, -100_001), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got, gotErr := spec.x.Add(spec.y) if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr, got) return } require.NoError(t, gotErr) assert.Equal(t, spec.exp, got) }) } } func TestSub(t *testing.T) { specs := map[string]struct { x Dec y Dec exp Dec expErr error }{ "0 - 0 = 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), exp: NewDecFromInt64(0), }, "0 - 123 = -123": { x: NewDecFromInt64(0), y: NewDecFromInt64(123), exp: NewDecFromInt64(-123), }, "0 - -123 = 123": { x: NewDecFromInt64(0), y: NewDecFromInt64(-123), exp: NewDecFromInt64(123), }, "123 - 123 = 0": { x: NewDecFromInt64(123), y: NewDecFromInt64(123), exp: NewDecFromInt64(0), }, "-123 - 123 = -246": { x: NewDecFromInt64(-123), y: NewDecFromInt64(123), exp: NewDecFromInt64(-246), }, "-123 - -123 = 0": { x: NewDecFromInt64(-123), y: NewDecFromInt64(-123), exp: NewDecFromInt64(0), }, "1.234 - 1.234 = 0.000": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(1234, -3), exp: NewDecWithExp(0, -3), }, "1.234 - 123 = -121.766": { x: NewDecWithExp(1234, -3), y: NewDecFromInt64(123), exp: NewDecWithExp(-121766, -3), }, "1.234 - -123 = 124.234": { x: NewDecWithExp(1234, -3), y: NewDecFromInt64(-123), exp: NewDecWithExp(124234, -3), }, "1.234 - -1.234 = 2.468": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(-1234, -3), exp: NewDecWithExp(2468, -3), }, "-1.234 - -1.234 = 2.468": { x: NewDecWithExp(-1234, -3), y: NewDecWithExp(-1234, -3), exp: NewDecWithExp(0, -3), }, "1 - 0.999 = 0.001 - rounding after comma": { x: NewDecFromInt64(1), y: NewDecWithExp(999, -3), exp: NewDecWithExp(1, -3), }, "1e100000 - 1^-1 -> Err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(1, -1), expErr: ErrInvalidDec, }, "1e100000 - 1^1-> Err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(1, -1), expErr: ErrInvalidDec, }, "upper exp limit exceeded": { x: NewDecWithExp(1, 100_001), y: NewDecWithExp(1, 100_001), expErr: ErrInvalidDec, }, "lower exp limit exceeded": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(1, -100_001), expErr: ErrInvalidDec, }, "1e100000 - 1 = 999..9": { x: NewDecWithExp(1, 100_000), y: NewDecFromInt64(1), exp: must(NewDecFromString(strings.Repeat("9", 100_000))), }, "1e100000 - 0 = 1e100000": { x: NewDecWithExp(1, 100_000), y: NewDecFromInt64(0), exp: must(NewDecFromString("1e100000")), }, "1e100001 - 0 -> err": { x: NewDecWithExp(1, 100_001), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "1e100000 - -1 -> 100..1": { x: NewDecWithExp(1, 100_000), y: must(NewDecFromString("-9e100000")), expErr: ErrInvalidDec, }, "1e-100000 - 0 = 1e-100000": { x: NewDecWithExp(1, -100_000), y: NewDecFromInt64(0), exp: must(NewDecFromString("1e-100000")), }, "1e-100001 - 0 -> err": { x: NewDecWithExp(1, -100_001), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "1e-100000 - -1 -> 0.000..01": { x: NewDecWithExp(1, -100_000), y: NewDecFromInt64(-1), exp: must(NewDecFromString("1." + strings.Repeat("0", 99999) + "1")), }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got, gotErr := spec.x.Sub(spec.y) if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr) return } require.NoError(t, gotErr) assert.True(t, spec.exp.Equal(got), got.String()) }) } } func TestQuo(t *testing.T) { specs := map[string]struct { src string x Dec y Dec exp Dec expErr error }{ "0 / 0 -> Err": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, " 0 / 123 = 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(123), exp: NewDecFromInt64(0), }, "123 / 0 = 0": { x: NewDecFromInt64(123), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "-123 / 0 = 0": { x: NewDecFromInt64(-123), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "123 / 123 = 1": { x: NewDecFromInt64(123), y: NewDecFromInt64(123), exp: must(NewDecFromString("1.000000000000000000000000000000000")), }, "-123 / 123 = -1": { x: NewDecFromInt64(-123), y: NewDecFromInt64(123), exp: must(NewDecFromString("-1.000000000000000000000000000000000")), }, "-123 / -123 = 1": { x: NewDecFromInt64(-123), y: NewDecFromInt64(-123), exp: must(NewDecFromString("1.000000000000000000000000000000000")), }, "1.234 / 1.234 = 1": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(1234, -3), exp: must(NewDecFromString("1.000000000000000000000000000000000")), }, "-1.234 / 1234 = -1": { x: NewDecWithExp(-1234, -3), y: NewDecWithExp(1234, -3), exp: must(NewDecFromString("-1.000000000000000000000000000000000")), }, "1.234 / -123 = 1.0100": { x: NewDecWithExp(1234, -3), y: NewDecFromInt64(-123), exp: must(NewDecFromString("-0.01003252032520325203252032520325203")), }, "1.234 / -1.234 = -1": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(-1234, -3), exp: must(NewDecFromString("-1.000000000000000000000000000000000")), }, "-1.234 / -1.234 = 1": { x: NewDecWithExp(-1234, -3), y: NewDecWithExp(-1234, -3), exp: must(NewDecFromString("1.000000000000000000000000000000000")), }, "3 / -9 = -0.3333...3 - round down": { x: NewDecFromInt64(3), y: NewDecFromInt64(-9), exp: must(NewDecFromString("-0.3333333333333333333333333333333333")), }, "4 / 9 = 0.4444...4 - round down": { x: NewDecFromInt64(4), y: NewDecFromInt64(9), exp: must(NewDecFromString("0.4444444444444444444444444444444444")), }, "5 / 9 = 0.5555...6 - round up": { x: NewDecFromInt64(5), y: NewDecFromInt64(9), exp: must(NewDecFromString("0.5555555555555555555555555555555556")), }, "6 / 9 = 0.6666...7 - round up": { x: NewDecFromInt64(6), y: NewDecFromInt64(9), exp: must(NewDecFromString("0.6666666666666666666666666666666667")), }, "7 / 9 = 0.7777...8 - round up": { x: NewDecFromInt64(7), y: NewDecFromInt64(9), exp: must(NewDecFromString("0.7777777777777777777777777777777778")), }, "8 / 9 = 0.8888...9 - round up": { x: NewDecFromInt64(8), y: NewDecFromInt64(9), exp: must(NewDecFromString("0.8888888888888888888888888888888889")), }, "9e-34 / 10 = 9e-35 - no rounding": { x: NewDecWithExp(9, -34), y: NewDecFromInt64(10), exp: must(NewDecFromString("9e-35")), }, "9e-35 / 10 = 9e-36 - no rounding": { x: NewDecWithExp(9, -35), y: NewDecFromInt64(10), exp: must(NewDecFromString("9e-36")), }, "high precision - min/0.1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(1, -1), exp: NewDecWithExp(1, -99_999), }, "high precision - min/1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(1, 0), exp: NewDecWithExp(1, -100_000), }, "high precision - min/10": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(1, 1), expErr: ErrInvalidDec, }, "high precision - <_min/0.1": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(1, -1), exp: NewDecWithExp(1, -100_000), }, "high precision - <_min/1": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(1, 0), expErr: ErrInvalidDec, }, "high precision - <_min/10": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(1, 1), expErr: ErrInvalidDec, }, "high precision - min/-0.1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(-1, -1), exp: NewDecWithExp(-1, -99_999), }, "high precision - min/-1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(-1, 0), exp: NewDecWithExp(-1, -100_000), }, "high precision - min/-10": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(-1, 1), expErr: ErrInvalidDec, }, "high precision - <_min/-0.1": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(-1, -1), exp: NewDecWithExp(-1, -100_000), }, "high precision - <_min/-1": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(-1, 0), expErr: ErrInvalidDec, }, "high precision - <_min/-10": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(-1, 1), expErr: ErrInvalidDec, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got, gotErr := spec.x.Quo(spec.y) if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr) return } require.NoError(t, gotErr) last35 := func(s string) string { var x int if len(s) < 36 { x = 0 } else { x = len(s) - 36 } return fmt.Sprintf("%s(%d)", s[x:], len(s)) } gotReduced, _ := got.Reduce() assert.True(t, spec.exp.Equal(gotReduced), "exp %s, got: %s", last35(spec.exp.String()), last35(gotReduced.String())) }) } } func TestQuoExact(t *testing.T) { specs := map[string]struct { src string x Dec y Dec exp Dec expErr error }{ "0 / 0 -> Err": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, " 0 / 123 = 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(123), exp: NewDecFromInt64(0), }, "123 / 0 -> Err": { x: NewDecFromInt64(123), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "-123 / 0 -> Err": { x: NewDecFromInt64(-123), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "123 / 123 = 1": { x: NewDecFromInt64(123), y: NewDecFromInt64(123), exp: must(NewDecFromString("1.000000000000000000000000000000000")), }, "-123 / 123 = 1": { x: NewDecFromInt64(-123), y: NewDecFromInt64(123), exp: must(NewDecFromString("-1.000000000000000000000000000000000")), }, "-123 / -123 = 1": { x: NewDecFromInt64(-123), y: NewDecFromInt64(-123), exp: must(NewDecFromString("1.000000000000000000000000000000000")), }, "1.234 / 1.234 = 1": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(1234, -3), exp: must(NewDecFromString("1.000000000000000000000000000000000")), }, "-1.234 / 1.234 = -1": { x: NewDecWithExp(-1234, -3), y: NewDecWithExp(1234, -3), exp: must(NewDecFromString("-1.000000000000000000000000000000000")), }, "1.234 / -123 -> Err": { x: NewDecWithExp(1234, -3), y: NewDecFromInt64(-123), expErr: ErrUnexpectedRounding, }, "1.234 / -1.234 = -1": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(-1234, -3), exp: must(NewDecFromString("-1.000000000000000000000000000000000")), }, "-1.234 / -1.234 = 1": { x: NewDecWithExp(-1234, -3), y: NewDecWithExp(-1234, -3), exp: must(NewDecFromString("1.000000000000000000000000000000000")), }, "3 / -9 -> Err": { x: NewDecFromInt64(3), y: NewDecFromInt64(-9), expErr: ErrUnexpectedRounding, }, "4 / 9 -> Err": { x: NewDecFromInt64(4), y: NewDecFromInt64(9), expErr: ErrUnexpectedRounding, }, "5 / 9 -> Err": { x: NewDecFromInt64(5), y: NewDecFromInt64(9), expErr: ErrUnexpectedRounding, }, "6 / 9 -> Err": { x: NewDecFromInt64(6), y: NewDecFromInt64(9), expErr: ErrUnexpectedRounding, }, "7 / 9 -> Err": { x: NewDecFromInt64(7), y: NewDecFromInt64(9), expErr: ErrUnexpectedRounding, }, "8 / 9 -> Err": { x: NewDecFromInt64(8), y: NewDecFromInt64(9), expErr: ErrUnexpectedRounding, }, "9e-34 / 10 = 9e-35 - no rounding": { x: NewDecWithExp(9, -34), y: NewDecFromInt64(10), exp: must(NewDecFromString("0.00000000000000000000000000000000009000000000000000000000000000000000")), }, "9e-35 / 10 = 9e-36 - no rounding": { x: NewDecWithExp(9, -35), y: NewDecFromInt64(10), exp: must(NewDecFromString("9e-36")), }, "high precision - min/0.1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(1, -1), exp: NewDecWithExp(1, -99_999), }, "high precision - min/1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(1, 0), exp: NewDecWithExp(1, -100_000), }, "high precision - min/10": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(1, 1), expErr: ErrInvalidDec, }, "high precision - <_min/0.1": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(1, -1), exp: NewDecWithExp(1, -100_000), }, "high precision - <_min/1": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(1, 0), expErr: ErrInvalidDec, }, "high precision - <_min/10 -> Err": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(1, 1), expErr: ErrInvalidDec, }, "high precision - min/-0.1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(-1, -1), exp: NewDecWithExp(-1, -99_999), }, "high precision - min/-1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(-1, 0), exp: NewDecWithExp(-1, -100_000), }, "high precision - min/-10 -> Err": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(-1, 1), expErr: ErrInvalidDec, }, "high precision - <_min/-0.1": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(-1, -1), exp: NewDecWithExp(-1, -100_000), }, "high precision - <_min/-1": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(-1, 0), expErr: ErrInvalidDec, }, "high precision - <_min/-10": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(-1, 1), expErr: ErrInvalidDec, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got, gotErr := spec.x.QuoExact(spec.y) if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr) return } require.NoError(t, gotErr) assert.True(t, spec.exp.Equal(got)) }) } } func TestQuoInteger(t *testing.T) { specs := map[string]struct { src string x Dec y Dec exp Dec expErr error }{ "0 / 0 -> Err": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, " 0 / 123 = 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(123), exp: NewDecFromInt64(0), }, "123 / 0 -> Err": { x: NewDecFromInt64(123), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "-123 / -> Err": { x: NewDecFromInt64(-123), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "123 / 123 = 1": { x: NewDecFromInt64(123), y: NewDecFromInt64(123), exp: NewDecFromInt64(1), }, "-123 / 123 = -1": { x: NewDecFromInt64(-123), y: NewDecFromInt64(123), exp: NewDecFromInt64(-1), }, "-123 / -123 = 1": { x: NewDecFromInt64(-123), y: NewDecFromInt64(-123), exp: NewDecFromInt64(1), }, "1.234 / 1.234": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(1234, -3), exp: NewDecFromInt64(1), }, "-1.234 / 1234 = -121.766": { x: NewDecWithExp(-1234, -3), y: NewDecWithExp(1234, -3), exp: NewDecFromInt64(-1), }, "1.234 / -1.234 = 2.468": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(-1234, -3), exp: NewDecFromInt64(-1), }, "-1.234 / -1.234 = 1": { x: NewDecWithExp(-1234, -3), y: NewDecWithExp(-1234, -3), exp: NewDecFromInt64(1), }, "3 / -9 = 0": { x: NewDecFromInt64(3), y: NewDecFromInt64(-9), exp: must(NewDecFromString("0")), }, "8 / 9 = 0": { x: NewDecFromInt64(8), y: NewDecFromInt64(9), exp: must(NewDecFromString("0")), }, "high precision - min/0.1": { x: NewDecWithExp(1, -100_000), y: NewDecWithExp(1, -1), exp: NewDecFromInt64(0), }, "high precision - <_min/-1 -> Err": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(-1, 0), expErr: ErrInvalidDec, }, "high precision - <_min/-10 -> Err": { x: NewDecWithExp(1, -100_001), y: NewDecWithExp(-1, 1), expErr: ErrInvalidDec, }, "1e000 / 1 -> Err": { x: NewDecWithExp(1, 100_000), y: NewDecFromInt64(1), expErr: ErrInvalidDec, }, "1e100000 - 1^1 -> Err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(1, -1), expErr: ErrInvalidDec, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got, gotErr := spec.x.QuoInteger(spec.y) if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr) return } require.NoError(t, gotErr) assert.True(t, spec.exp.Equal(got)) }) } } func TestModulo(t *testing.T) { specs := map[string]struct { x Dec y Dec exp Dec expErr error }{ "0 / 123 = 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(123), exp: NewDecFromInt64(0), }, "123 / 10 = 3": { x: NewDecFromInt64(123), y: NewDecFromInt64(10), exp: NewDecFromInt64(3), }, "123 / -10 = 3": { x: NewDecFromInt64(123), y: NewDecFromInt64(-10), exp: NewDecFromInt64(3), }, "-123 / 10 = -3": { x: NewDecFromInt64(-123), y: NewDecFromInt64(10), exp: NewDecFromInt64(-3), }, "1.234 / 1 = 0.234": { x: NewDecWithExp(1234, -3), y: NewDecFromInt64(1), exp: NewDecWithExp(234, -3), }, "1.234 / 0.1 = 0.034": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(1, -1), exp: NewDecWithExp(34, -3), }, "1.234 / 1.1 = 0.134": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(11, -1), exp: NewDecWithExp(134, -3), }, "10 / 0 -> Err": { x: NewDecFromInt64(10), y: NewDecFromInt64(0), expErr: ErrInvalidDec, }, "-1e0000 / 9e0000 = 1e0000": { x: NewDecWithExp(-1, 100_000), y: NewDecWithExp(9, 100_000), exp: NewDecWithExp(-1, 100_000), }, "1e0000 / 9e0000 = 1e0000": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(9, 100_000), exp: NewDecWithExp(1, 100_000), }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got, gotErr := spec.x.Modulo(spec.y) if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr) return } require.NoError(t, gotErr) assert.True(t, spec.exp.Equal(got)) }) } } func TestNumDecimalPlaces(t *testing.T) { specs := map[string]struct { src Dec exp uint32 }{ "integer": { src: NewDecFromInt64(123), exp: 0, }, "one decimal place": { src: NewDecWithExp(1234, -1), exp: 1, }, "two decimal places": { src: NewDecWithExp(12345, -2), exp: 2, }, "three decimal places": { src: NewDecWithExp(123456, -3), exp: 3, }, "trailing zeros": { src: NewDecWithExp(123400, -4), exp: 4, }, "zero value": { src: NewDecFromInt64(0), exp: 0, }, "negative value": { src: NewDecWithExp(-12345, -3), exp: 3, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got := spec.src.NumDecimalPlaces() assert.Equal(t, spec.exp, got) }) } } func TestCmp(t *testing.T) { specs := map[string]struct { x Dec y Dec exp int }{ "0 == 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), exp: 0, }, "0 < 123 = -1": { x: NewDecFromInt64(0), y: NewDecFromInt64(123), exp: -1, }, "123 > 0 = 1": { x: NewDecFromInt64(123), y: NewDecFromInt64(0), exp: 1, }, "-123 < 0 = -1": { x: NewDecFromInt64(-123), y: NewDecFromInt64(0), exp: -1, }, "123 == 123": { x: NewDecFromInt64(123), y: NewDecFromInt64(123), exp: 0, }, "-123 == -123": { x: NewDecFromInt64(-123), y: NewDecFromInt64(-123), exp: 0, }, "1.234 == 1.234": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(1234, -3), exp: 0, }, "1.234 > 1.233": { x: NewDecWithExp(1234, -3), y: NewDecWithExp(1233, -3), exp: 1, }, "1.233 < 1.234": { x: NewDecWithExp(1233, -3), y: NewDecWithExp(1234, -3), exp: -1, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got := spec.x.Cmp(spec.y) assert.Equal(t, spec.exp, got) }) } } func TestReduce(t *testing.T) { specs := map[string]struct { src string exp string decPlaces int }{ "positive value": { src: "10", exp: "10", decPlaces: 1, }, "negative value": { src: "-10", exp: "-10", decPlaces: 1, }, "positive decimal": { src: "1.30000", exp: "1.3", decPlaces: 4, }, "negative decimal": { src: "-1.30000", exp: "-1.3", decPlaces: 4, }, "zero decimal and decimal places": { src: "0.00000", exp: "0", decPlaces: 0, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { src := must(NewDecFromString(spec.src)) got, gotZerosRemoved := src.Reduce() assert.Equal(t, spec.decPlaces, gotZerosRemoved) assert.Equal(t, spec.exp, got.String()) }) } } func TestMulExact(t *testing.T) { specs := map[string]struct { x Dec y Dec exp Dec expErr error }{ "200 * 200 = 40000": { x: NewDecFromInt64(200), y: NewDecFromInt64(200), exp: NewDecFromInt64(40000), }, "-200 * -200 = 40000": { x: NewDecFromInt64(-200), y: NewDecFromInt64(-200), exp: NewDecFromInt64(40000), }, "-100 * -100 = 10000": { x: NewDecFromInt64(-100), y: NewDecFromInt64(-100), exp: NewDecFromInt64(10000), }, "0 * 0 = 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), exp: NewDecFromInt64(0), }, "1.1 * 1.1 = 1.21": { x: NewDecWithExp(11, -1), y: NewDecWithExp(11, -1), exp: NewDecWithExp(121, -2), }, "1.000 * 1.000 = 1.000000": { x: NewDecWithExp(1000, -3), y: NewDecWithExp(1000, -3), exp: must(NewDecFromString("1.000000")), }, "0.0000001 * 0.0000001 = 0": { x: NewDecWithExp(0o0000001, -7), y: NewDecWithExp(0o0000001, -7), exp: NewDecWithExp(1, -14), }, "0.12345678901234567890123456789012345 * 1": { x: must(NewDecFromString("0.12345678901234567890123456789012345")), y: NewDecWithExp(1, 0), expErr: ErrUnexpectedRounding, }, "0.12345678901234567890123456789012345 * 0": { x: must(NewDecFromString("0.12345678901234567890123456789012345")), y: NewDecFromInt64(0), exp: NewDecFromInt64(0), }, "0.12345678901234567890123456789012345 * 0.1": { x: must(NewDecFromString("0.12345678901234567890123456789012345")), y: NewDecWithExp(1, -1), expErr: ErrUnexpectedRounding, }, "1000001 * 1.000001 = 1000002.000001": { x: NewDecFromInt64(1000001), y: NewDecWithExp(1000001, -6), exp: must(NewDecFromString("1000002.000001")), }, "1000001 * 1000000 = 1000001000000 ": { x: NewDecFromInt64(1000001), y: NewDecFromInt64(1000000), exp: NewDecFromInt64(1000001000000), }, "1e0000 * 1e0000 -> Err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(1, 100_000), expErr: ErrInvalidDec, }, "1e0000 * 1 = 1e0000": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(1, 0), exp: NewDecWithExp(1, 100_000), }, "1e100000 * 9 = 9e100000": { x: NewDecWithExp(1, 100_000), y: NewDecFromInt64(9), exp: NewDecWithExp(9, 100_000), }, "1e100000 * 10 = err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(1, 1), expErr: ErrInvalidDec, }, "1e0000 * -1 = -1e0000": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(-1, 0), exp: NewDecWithExp(-1, 100_000), }, "1e100000 * -9 = 9e100000": { x: NewDecWithExp(1, 100_000), y: NewDecFromInt64(-9), exp: NewDecWithExp(-9, 100_000), }, "1e100000 * -10 = err": { x: NewDecWithExp(1, 100_000), y: NewDecWithExp(-1, 1), expErr: ErrInvalidDec, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { got, gotErr := spec.x.MulExact(spec.y) if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr, gotErr) return } require.NoError(t, gotErr) assert.True(t, spec.exp.Equal(got), "exp: %s, got: %s", spec.exp.Text('E'), got.Text('E')) }) } } func TestToBigInt(t *testing.T) { i1 := "1000000000000000000000000000000000000123456789" tcs := []struct { intStr string out string isError error }{ {i1, i1, nil}, {"1000000000000000000000000000000000000123456789.00000000", i1, nil}, {"123.456e6", "123456000", nil}, {"12345.6", "", ErrNonIntegral}, } for idx, tc := range tcs { t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { a, err := NewDecFromString(tc.intStr) require.NoError(t, err) b, err := a.BigInt() if tc.isError == nil { require.NoError(t, err, "test_%d", idx) require.Equal(t, tc.out, b.String(), "test_%d", idx) } else { require.ErrorIs(t, err, tc.isError, "test_%d", idx) } }) } } func TestToSdkInt(t *testing.T) { maxIntValue := "115792089237316195423570985008687907853269984665640564039457584007913129639935" // 2^256 -1 tcs := []struct { src string exp string expErr bool }{ {src: maxIntValue, exp: maxIntValue}, {src: "1000000000000000000000000000000000000123456789.00000001", exp: "1000000000000000000000000000000000000123456789"}, {src: "123.456e6", exp: "123456000"}, {src: "123.456e1", exp: "1234"}, {src: "123.456", exp: "123"}, {src: "123.956", exp: "123"}, {src: "-123.456", exp: "-123"}, {src: "-123.956", exp: "-123"}, {src: "-0.956", exp: "0"}, {src: "-0.9", exp: "0"}, {src: "1E-100000", exp: "0"}, {src: "115792089237316195423570985008687907853269984665640564039457584007913129639936", expErr: true}, // 2^256 {src: "1E100000", expErr: true}, } for _, tc := range tcs { t.Run(fmt.Sprint(tc.src), func(t *testing.T) { a, err := NewDecFromString(tc.src) require.NoError(t, err) b, gotErr := a.SdkIntTrim() if tc.expErr { require.Error(t, gotErr, "value: %s", b.String()) return } require.NoError(t, gotErr) require.Equal(t, tc.exp, b.String()) }) } } func TestInfDecString(t *testing.T) { _, err := NewDecFromString("iNf") require.Error(t, err) require.ErrorIs(t, err, ErrInvalidDec) } func must[T any](r T, err error) T { if err != nil { panic(err) } return r } func TestMarshalUnmarshal(t *testing.T) { specs := map[string]struct { x Dec exp string expErr error }{ "Zero value": { x: NewDecFromInt64(0), exp: "0", }, "-0": { x: NewDecFromInt64(-0), exp: "0", }, "1 decimal place": { x: must(NewDecFromString("0.1")), exp: "0.1", }, "2 decimal places": { x: must(NewDecFromString("0.01")), exp: "0.01", }, "3 decimal places": { x: must(NewDecFromString("0.001")), exp: "0.001", }, "4 decimal places": { x: must(NewDecFromString("0.0001")), exp: "0.0001", }, "5 decimal places": { x: must(NewDecFromString("0.00001")), exp: "0.00001", }, "6 decimal places": { x: must(NewDecFromString("0.000001")), exp: "1E-6", }, "7 decimal places": { x: must(NewDecFromString("0.0000001")), exp: "1E-7", }, "1": { x: must(NewDecFromString("1")), exp: "1", }, "12": { x: must(NewDecFromString("12")), exp: "12", }, "123": { x: must(NewDecFromString("123")), exp: "123", }, "1234": { x: must(NewDecFromString("1234")), exp: "1234", }, "12345": { x: must(NewDecFromString("12345")), exp: "12345", }, "123456": { x: must(NewDecFromString("123456")), exp: "123456", }, "1234567": { x: must(NewDecFromString("1234567")), exp: "1.234567E+6", }, "12345678": { x: must(NewDecFromString("12345678")), exp: "12.345678E+6", }, "123456789": { x: must(NewDecFromString("123456789")), exp: "123.456789E+6", }, "1234567890": { x: must(NewDecFromString("1234567890")), exp: "123.456789E+7", }, "12345678900": { x: must(NewDecFromString("12345678900")), exp: "123.456789E+8", }, "negative 1 with negative exponent": { x: must(NewDecFromString("-1.000001")), exp: "-1.000001", }, "-1.0000001 - negative 1 with negative exponent": { x: must(NewDecFromString("-1.0000001")), exp: "-1.0000001", }, "3 decimal places before the comma": { x: must(NewDecFromString("100")), exp: "100", }, "4 decimal places before the comma": { x: must(NewDecFromString("1000")), exp: "1000", }, "5 decimal places before the comma": { x: must(NewDecFromString("10000")), exp: "10000", }, "6 decimal places before the comma": { x: must(NewDecFromString("100000")), exp: "100000", }, "7 decimal places before the comma": { x: must(NewDecFromString("1000000")), exp: "1E+6", }, "1e100000": { x: NewDecWithExp(1, 100_000), exp: "1E+100000", }, "1.1e100000": { x: must(NewDecFromString("1.1e100000")), exp: "1.1E+100000", }, "1e100001": { x: NewDecWithExp(1, 100_001), expErr: ErrInvalidDec, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { marshaled, gotErr := spec.x.Marshal() if spec.expErr != nil { require.ErrorIs(t, gotErr, spec.expErr) return } require.NoError(t, gotErr) assert.Equal(t, spec.exp, string(marshaled)) // and backwards unmarshalledDec := new(Dec) require.NoError(t, unmarshalledDec.Unmarshal(marshaled)) assert.Equal(t, spec.exp, unmarshalledDec.String()) assert.True(t, spec.x.Equal(*unmarshalledDec)) }) } }