goext/mongo/bson/primitive/decimal_test.go

237 lines
10 KiB
Go

// Copyright (C) MongoDB, Inc. 2022-present.
//
// Licensed 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
package primitive
import (
"encoding/json"
"fmt"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type bigIntTestCase struct {
s string
h uint64
l uint64
bi *big.Int
exp int
remark string
}
func parseBigInt(s string) *big.Int {
bi, _ := new(big.Int).SetString(s, 10)
return bi
}
var (
one = big.NewInt(1)
biMaxS = new(big.Int).SetBytes([]byte{0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
biNMaxS = new(big.Int).Neg(biMaxS)
biOverflow = new(big.Int).Add(biMaxS, one)
biNOverflow = new(big.Int).Neg(biOverflow)
bi12345 = parseBigInt("12345")
biN12345 = parseBigInt("-12345")
bi9_14 = parseBigInt("90123456789012")
biN9_14 = parseBigInt("-90123456789012")
bi9_34 = parseBigInt("9999999999999999999999999999999999")
biN9_34 = parseBigInt("-9999999999999999999999999999999999")
)
var bigIntTestCases = []bigIntTestCase{
{s: "12345", h: 0x3040000000000000, l: 12345, bi: bi12345},
{s: "-12345", h: 0xB040000000000000, l: 12345, bi: biN12345},
{s: "90123456.789012", h: 0x3034000000000000, l: 90123456789012, bi: bi9_14, exp: -6},
{s: "-90123456.789012", h: 0xB034000000000000, l: 90123456789012, bi: biN9_14, exp: -6},
{s: "9.0123456789012E+22", h: 0x3052000000000000, l: 90123456789012, bi: bi9_14, exp: 9},
{s: "-9.0123456789012E+22", h: 0xB052000000000000, l: 90123456789012, bi: biN9_14, exp: 9},
{s: "9.0123456789012E-8", h: 0x3016000000000000, l: 90123456789012, bi: bi9_14, exp: -21},
{s: "-9.0123456789012E-8", h: 0xB016000000000000, l: 90123456789012, bi: biN9_14, exp: -21},
{s: "9999999999999999999999999999999999", h: 3477321013416265664, l: 4003012203950112767, bi: bi9_34},
{s: "-9999999999999999999999999999999999", h: 12700693050271041472, l: 4003012203950112767, bi: biN9_34},
{s: "0.9999999999999999999999999999999999", h: 3458180714999941056, l: 4003012203950112767, bi: bi9_34, exp: -34},
{s: "-0.9999999999999999999999999999999999", h: 12681552751854716864, l: 4003012203950112767, bi: biN9_34, exp: -34},
{s: "99999999999999999.99999999999999999", h: 3467750864208103360, l: 4003012203950112767, bi: bi9_34, exp: -17},
{s: "-99999999999999999.99999999999999999", h: 12691122901062879168, l: 4003012203950112767, bi: biN9_34, exp: -17},
{s: "9.999999999999999999999999999999999E+35", h: 3478446913323108288, l: 4003012203950112767, bi: bi9_34, exp: 2},
{s: "-9.999999999999999999999999999999999E+35", h: 12701818950177884096, l: 4003012203950112767, bi: biN9_34, exp: 2},
{s: "9.999999999999999999999999999999999E+40", h: 3481261663090214848, l: 4003012203950112767, bi: bi9_34, exp: 7},
{s: "-9.999999999999999999999999999999999E+40", h: 12704633699944990656, l: 4003012203950112767, bi: biN9_34, exp: 7},
{s: "99999999999999999999999999999.99999", h: 3474506263649159104, l: 4003012203950112767, bi: bi9_34, exp: -5},
{s: "-99999999999999999999999999999.99999", h: 12697878300503934912, l: 4003012203950112767, bi: biN9_34, exp: -5},
{s: "1.038459371706965525706099265844019E-6143", remark: "subnormal", h: 0x333333333333, l: 0x3333333333333333, bi: parseBigInt("10384593717069655257060992658440190"), exp: MinDecimal128Exp - 1},
{s: "-1.038459371706965525706099265844019E-6143", remark: "subnormal", h: 0x8000333333333333, l: 0x3333333333333333, bi: parseBigInt("-10384593717069655257060992658440190"), exp: MinDecimal128Exp - 1},
{s: "rounding overflow 1", remark: "overflow", bi: parseBigInt("103845937170696552570609926584401910"), exp: MaxDecimal128Exp},
{s: "rounding overflow 2", remark: "overflow", bi: parseBigInt("103845937170696552570609926584401910"), exp: MaxDecimal128Exp},
{s: "subnormal overflow 1", remark: "overflow", bi: biMaxS, exp: MinDecimal128Exp - 1},
{s: "subnormal overflow 2", remark: "overflow", bi: biNMaxS, exp: MinDecimal128Exp - 1},
{s: "clamped overflow 1", remark: "overflow", bi: biMaxS, exp: MaxDecimal128Exp + 1},
{s: "clamped overflow 2", remark: "overflow", bi: biNMaxS, exp: MaxDecimal128Exp + 1},
{s: "biMaxS+1 overflow", remark: "overflow", bi: biOverflow, exp: MaxDecimal128Exp},
{s: "biNMaxS-1 overflow", remark: "overflow", bi: biNOverflow, exp: MaxDecimal128Exp},
{s: "NaN", h: 0x7c00000000000000, l: 0, remark: "NaN"},
{s: "Infinity", h: 0x7800000000000000, l: 0, remark: "Infinity"},
{s: "-Infinity", h: 0xf800000000000000, l: 0, remark: "-Infinity"},
}
func TestDecimal128_BigInt(t *testing.T) {
for _, c := range bigIntTestCases {
t.Run(c.s, func(t *testing.T) {
switch c.remark {
case "NaN", "Infinity", "-Infinity":
d128 := NewDecimal128(c.h, c.l)
_, _, err := d128.BigInt()
require.Error(t, err, "case %s", c.s)
case "":
d128 := NewDecimal128(c.h, c.l)
bi, e, err := d128.BigInt()
require.NoError(t, err, "case %s", c.s)
require.Equal(t, 0, c.bi.Cmp(bi), "case %s e:%s a:%s", c.s, c.bi.String(), bi.String())
require.Equal(t, c.exp, e, "case %s", c.s, d128.String())
}
})
}
}
func TestParseDecimal128FromBigInt(t *testing.T) {
for _, c := range bigIntTestCases {
switch c.remark {
case "overflow":
d128, ok := ParseDecimal128FromBigInt(c.bi, c.exp)
require.Equal(t, false, ok, "case %s %s", c.s, d128.String(), c.remark)
case "", "rounding", "subnormal", "clamped":
d128, ok := ParseDecimal128FromBigInt(c.bi, c.exp)
require.Equal(t, true, ok, "case %s", c.s)
require.Equal(t, c.s, d128.String(), "case %s", c.s)
require.Equal(t, c.h, d128.h, "case %s", c.s, d128.l)
require.Equal(t, c.l, d128.l, "case %s", c.s, d128.h)
}
}
}
func TestParseDecimal128(t *testing.T) {
cases := make([]bigIntTestCase, 0, len(bigIntTestCases))
cases = append(cases, bigIntTestCases...)
cases = append(cases,
bigIntTestCase{s: "-0001231.453454000000565600000000E-21", h: 0xafe6000003faa269, l: 0x81cfeceaabdb1800},
bigIntTestCase{s: "12345E+21", h: 0x306a000000000000, l: 12345},
bigIntTestCase{s: "0.10000000000000000000000000000000000000000001", remark: "parse fail"},
bigIntTestCase{s: ".125e1", h: 0x303c000000000000, l: 125},
bigIntTestCase{s: ".125", h: 0x303a000000000000, l: 125},
// Test that parsing negative zero returns negative zero with a zero exponent.
bigIntTestCase{s: "-0", h: 0xb040000000000000, l: 0},
// Test that parsing negative zero with an in-range exponent returns negative zero and
// preserves the specified exponent value.
bigIntTestCase{s: "-0E999", h: 0xb80e000000000000, l: 0},
// Test that parsing zero with an out-of-range positive exponent returns zero with the
// maximum positive exponent (i.e. 0e+6111).
bigIntTestCase{s: "0E2000000000000", h: 0x5ffe000000000000, l: 0},
// Test that parsing zero with an out-of-range negative exponent returns zero with the
// minimum negative exponent (i.e. 0e-6176).
bigIntTestCase{s: "-0E2000000000000", h: 0xdffe000000000000, l: 0},
bigIntTestCase{s: "", remark: "parse fail"})
for _, c := range cases {
t.Run(c.s, func(t *testing.T) {
switch c.remark {
case "overflow", "parse fail":
_, err := ParseDecimal128(c.s)
assert.Error(t, err, "ParseDecimal128(%q) should return an error", c.s)
default:
got, err := ParseDecimal128(c.s)
require.NoError(t, err, "ParseDecimal128(%q) error", c.s)
want := Decimal128{h: c.h, l: c.l}
// Decimal128 doesn't implement an equality function, so compare the expected
// low/high uint64 values directly. Also print the string representation of each
// number to make debugging failures easier.
assert.Equal(t, want, got, "ParseDecimal128(%q) = %s, want %s", c.s, got, want)
}
})
}
}
func TestDecimal128_JSON(t *testing.T) {
t.Run("roundTrip", func(t *testing.T) {
decimal := NewDecimal128(0x3040000000000000, 12345)
bytes, err := json.Marshal(decimal)
assert.Nil(t, err, "json.Marshal error: %v", err)
got := NewDecimal128(0, 0)
err = json.Unmarshal(bytes, &got)
assert.Nil(t, err, "json.Unmarshal error: %v", err)
assert.Equal(t, decimal.h, got.h, "expected h: %v got: %v", decimal.h, got.h)
assert.Equal(t, decimal.l, got.l, "expected l: %v got: %v", decimal.l, got.l)
})
t.Run("unmarshal extendedJSON", func(t *testing.T) {
want := NewDecimal128(0x3040000000000000, 12345)
extJSON := fmt.Sprintf(`{"$numberDecimal": %q}`, want.String())
got := NewDecimal128(0, 0)
err := json.Unmarshal([]byte(extJSON), &got)
assert.Nil(t, err, "json.Unmarshal error: %v", err)
assert.Equal(t, want.h, got.h, "expected h: %v got: %v", want.h, got.h)
assert.Equal(t, want.l, got.l, "expected l: %v got: %v", want.l, got.l)
})
t.Run("unmarshal null", func(t *testing.T) {
want := NewDecimal128(0, 0)
extJSON := `null`
got := NewDecimal128(0, 0)
err := json.Unmarshal([]byte(extJSON), &got)
assert.Nil(t, err, "json.Unmarshal error: %v", err)
assert.Equal(t, want.h, got.h, "expected h: %v got: %v", want.h, got.h)
assert.Equal(t, want.l, got.l, "expected l: %v got: %v", want.l, got.l)
})
t.Run("unmarshal", func(t *testing.T) {
cases := make([]bigIntTestCase, 0, len(bigIntTestCases))
cases = append(cases, bigIntTestCases...)
cases = append(cases,
bigIntTestCase{s: "-0001231.453454000000565600000000E-21", h: 0xafe6000003faa269, l: 0x81cfeceaabdb1800},
bigIntTestCase{s: "12345E+21", h: 0x306a000000000000, l: 12345},
bigIntTestCase{s: "0.10000000000000000000000000000000000000000001", remark: "parse fail"},
bigIntTestCase{s: ".125e1", h: 0x303c000000000000, l: 125},
bigIntTestCase{s: ".125", h: 0x303a000000000000, l: 125})
for _, c := range cases {
t.Run(c.s, func(t *testing.T) {
input := fmt.Sprintf(`{"foo": %q}`, c.s)
var got map[string]Decimal128
err := json.Unmarshal([]byte(input), &got)
switch c.remark {
case "overflow", "parse fail":
assert.NotNil(t, err, "expected Unmarshal error, got nil")
default:
assert.Nil(t, err, "Unmarshal error: %v", err)
gotDecimal := got["foo"]
assert.Equal(t, c.h, gotDecimal.h, "expected h: %v got: %v", c.h, gotDecimal.l)
assert.Equal(t, c.l, gotDecimal.l, "expected l: %v got: %v", c.l, gotDecimal.h)
}
})
}
})
}