Skip to content

Commit

Permalink
feat: add parse mode (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
quagmt authored Feb 19, 2025
1 parent 751d5a2 commit 89217c6
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 19 deletions.
75 changes: 57 additions & 18 deletions bint.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ import (
"strings"
)

var (
defaultParseMode = ParseModeError
)

func SetDefaultParseMode(mode ParseMode) {
switch mode {
case ParseModeError, ParseModeTrunc:
defaultParseMode = mode
default:
panic("can't set default parse mode: invalid mode value")
}
}

type ParseMode int

const (
// Default parse mode will return error if the string number
// has more than [defaultPrec] decimal digits.
ParseModeError ParseMode = iota

// ParseModeTrunc will not return error if the string number
// has more than [defaultPrec] decimal digits and truncate the exceeded digits instead.
// Use this mode if the data source (e.g. database, external API, etc.) stores data
// that has more than [defaultPrec] decimal digits already, allowing for some
// precision loss (if acceptable).
ParseModeTrunc
)

const (
// maxDigitU64 is the maximum digits of a number
// that can be safely stored in a uint64.
Expand Down Expand Up @@ -127,26 +155,32 @@ func parseBint(s []byte) (bool, bint, uint8, error) {
return false, bint{}, 0, errInvalidFormat(s)
}

pIndex := -1
vLen := len(value)
for i := 0; i < vLen; i++ {
if value[i] == '.' {
if pIndex > -1 {
// input has more than 1 decimal point
return false, bint{}, 0, errInvalidFormat(s)
}
pIndex = i
}
}
pIndex := bytes.IndexByte(value, '.')

switch {
case pIndex == -1:
// There is no decimal point, we can just parse the original string as an int
intString = string(value)
case pIndex >= vLen-1:
case pIndex == 0 || pIndex >= vLen-1:
// prevent "123." or "-123."
return false, bint{}, 0, errInvalidFormat(s)
default:
prec = vLen - pIndex - 1
switch defaultParseMode {
case ParseModeError:
if prec > int(defaultPrec) {
return false, bint{}, 0, ErrPrecOutOfRange
}
case ParseModeTrunc:
if prec > int(defaultPrec) {
value = value[:pIndex+1+int(defaultPrec)]
prec = int(defaultPrec)
}
default:
return false, bint{}, 0, fmt.Errorf("invalid parse mode: %d. Make sure to use SetParseMode with a valid value", defaultParseMode)
}

b := strings.Builder{}
_, err := b.Write(value[:pIndex])
if err != nil {
Expand All @@ -160,11 +194,6 @@ func parseBint(s []byte) (bool, bint, uint8, error) {

// intString = value[:pIndex] + value[pIndex+1:]
intString = b.String()
prec = len(value[pIndex+1:])
}

if prec > int(defaultPrec) {
return false, bint{}, 0, ErrPrecOutOfRange
}

dValue := new(big.Int)
Expand Down Expand Up @@ -295,8 +324,18 @@ func parseLargeToU128(s []byte) (u128, uint8, error) {
// now 0 < pos < l-1
//nolint:gosec // l < maxStrLen, so 0 < l-pos-1 < 256, can be safely converted to uint8
prec := uint8(l - pos - 1)
if prec > defaultPrec {
return u128{}, 0, ErrPrecOutOfRange
switch defaultParseMode {
case ParseModeError:
if prec > defaultPrec {
return u128{}, 0, ErrPrecOutOfRange
}
case ParseModeTrunc:
if prec > defaultPrec {
s = s[:pos+1+int(defaultPrec)]
prec = defaultPrec
}
default:
return u128{}, 0, fmt.Errorf("invalid parse mode: %d. Make sure to use SetParseMode with a valid value", defaultParseMode)
}

// number has a decimal point, split into 2 parts: integer and fraction
Expand Down
87 changes: 87 additions & 0 deletions bint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package udecimal

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
)

func TestSetDefaultParseMode(t *testing.T) {
require.Equal(t, ParseModeError, defaultParseMode)

SetDefaultParseMode(ParseModeTrunc)
require.Equal(t, ParseModeTrunc, defaultParseMode)

SetDefaultParseMode(ParseModeError)
require.Equal(t, ParseModeError, defaultParseMode)

// expect panic if prec is 0
require.PanicsWithValue(t, "can't set default parse mode: invalid mode value", func() {
SetDefaultParseMode(2)
})
}

func TestParseModeTrunc(t *testing.T) {
defer SetDefaultParseMode(ParseModeError)

SetDefaultParseMode(ParseModeTrunc)

testcases := []struct {
input string
want string
}{
{
input: "1.123456789012345678999",
want: "1.1234567890123456789",
},
{
input: "-1.123456789012345678999",
want: "-1.1234567890123456789",
},
{
input: "1.123",
want: "1.123",
},
{
input: "-1.123",
want: "-1.123",
},
{
input: "12324564654613213216546546132131265.123456789012345678999",
want: "12324564654613213216546546132131265.1234567890123456789",
},
{
input: "-12324564654613213216546546132131265.123456789012345678999",
want: "-12324564654613213216546546132131265.1234567890123456789",
},
}

for _, tc := range testcases {
t.Run(fmt.Sprintf("parse %s with mode trunc", tc.input), func(t *testing.T) {
a := MustParse(tc.input)

require.Equal(t, tc.want, a.String())
})
}
}

func TestInvalidParseMode(t *testing.T) {
defer SetDefaultParseMode(ParseModeError)

defaultParseMode = 2

testcases := []string{
"1.123456789012345678999",
"-1.123456789012345678999",
"12324564654613213216546546132131265.123456789012345678999",
"-12324564654613213216546546132131265.123456789012345678999",
}

for _, tc := range testcases {
t.Run(fmt.Sprintf("parse %s with mode trunc", tc), func(t *testing.T) {
_, err := Parse(tc)
require.EqualError(t, err, "invalid parse mode: 2. Make sure to use SetParseMode with a valid value")
})
}
}
2 changes: 1 addition & 1 deletion decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func TestParse(t *testing.T) {
{".1234567890123456789012345678901234567890123456", "", fmt.Errorf("%w: can't parse '.1234567890123456789012345678901234567890123456'", ErrInvalidFormat)},
{"+.1234567890123456789012345678901234567890123456", "", fmt.Errorf("%w: can't parse '+.1234567890123456789012345678901234567890123456'", ErrInvalidFormat)},
{"-.1234567890123456789012345678901234567890123456", "", fmt.Errorf("%w: can't parse '-.1234567890123456789012345678901234567890123456'", ErrInvalidFormat)},
{"1.12345678901234567890123.45678901234567890123456", "", fmt.Errorf("%w: can't parse '1.12345678901234567890123.45678901234567890123456'", ErrInvalidFormat)},
{"1.12345678903.456", "", fmt.Errorf("%w: can't parse '1.12345678903.456'", ErrInvalidFormat)},
{"340282366920938463463374607431768211459.123+--", "", fmt.Errorf("%w: can't parse '340282366920938463463374607431768211459.123+--'", ErrInvalidFormat)},
{"", "", ErrEmptyString},
{"1.234567890123456789012348901", "", ErrPrecOutOfRange},
Expand Down

0 comments on commit 89217c6

Please sign in to comment.