Skip to content

Commit

Permalink
fix: magic number in parsing (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
quagmt authored Oct 27, 2024
1 parent 77b7f6e commit 1d8c752
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 49 deletions.
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ run:

linters:
enable:
- nolintlint
- errcheck
- gosimple
- goimports
Expand Down
43 changes: 21 additions & 22 deletions bint.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package udecimal

import (
"bytes"
"fmt"
"math/big"
"strings"
)

const (
// maxDigitU64 is the maximum digits of a number
// that can be safely stored in a uint64.
maxDigitU64 = 19
)

var (
bigZero = big.NewInt(0)
bigOne = big.NewInt(1)
Expand Down Expand Up @@ -171,7 +178,7 @@ func parseBint(s []byte) (bool, bint, uint8, error) {
return false, bint{}, 0, errInvalidFormat(s)
}

// nolint: gosec
//nolint:gosec
return neg, bintFromBigInt(dValue), uint8(prec), nil
}

Expand Down Expand Up @@ -211,7 +218,7 @@ func parseBintFromU128(s []byte) (bool, bint, uint8, error) {
prec uint8
)

if len(s[pos:]) <= 19 {
if len(s[pos:]) <= maxDigitU64 {
coef, prec, err = parseSmallToU128(s[pos:])
} else {
coef, prec, err = parseLargeToU128(s[pos:])
Expand All @@ -237,7 +244,7 @@ func parseSmallToU128(s []byte) (u128, uint8, error) {
return u128{}, 0, ErrInvalidFormat
}

// nolint: gosec
//nolint:gosec
prec = uint8(len(s) - i - 1)

// prevent "123." or "-123."
Expand Down Expand Up @@ -269,27 +276,12 @@ func parseSmallToU128(s []byte) (u128, uint8, error) {
func parseLargeToU128(s []byte) (u128, uint8, error) {
// find '.' position
l := len(s)
pos := -1

for i := 0; i < len(s); i++ {
if s[i] == '.' {
pos = i
}
}

pos := bytes.IndexByte(s, '.')
if pos == 0 || pos == l-1 {
// prevent ".123" or "123."
return u128{}, 0, ErrInvalidFormat
}

var prec uint8
if pos != -1 {
// nolint: gosec
prec = uint8(l - pos - 1)
if prec > defaultPrec {
return u128{}, 0, ErrPrecOutOfRange
}
}

if pos == -1 {
// no decimal point
coef, err := digitToU128(s)
Expand All @@ -300,14 +292,21 @@ func parseLargeToU128(s []byte) (u128, uint8, error) {
return coef, 0, nil
}

// now 0 < pos < l-1
//nolint:gosec
prec := uint8(l - pos - 1)
if prec > defaultPrec {
return u128{}, 0, ErrPrecOutOfRange
}

// number has a decimal point, split into 2 parts: integer and fraction
intPart, err := digitToU128(s[:pos])
if err != nil {
return u128{}, 0, err
}

// because max prec is 19,
// factionPart can't be larger than 10%20-1 and will fit into uint64 (fractionPart.hi == 0)
// factionPart can't be larger than 10^19-1 and will fit into uint64 (fractionPart.hi == 0)
fractionPart, err := digitToU128(s[pos+1:])
if err != nil {
return u128{}, 0, err
Expand All @@ -328,7 +327,7 @@ func parseLargeToU128(s []byte) (u128, uint8, error) {
}

func digitToU128(s []byte) (u128, error) {
if len(s) <= 19 {
if len(s) <= maxDigitU64 {
var u uint64
for i := 0; i < len(s); i++ {
if s[i] < '0' || s[i] > '9' {
Expand Down
4 changes: 1 addition & 3 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ func (d Decimal) fillBuffer(buf []byte, trimTrailingZeros bool) int {
for ; rem != 0; rem /= 10 {
n++

// nolint: gosec
buf[l-n] = byte(rem%10) + '0'
}

Expand All @@ -188,8 +187,7 @@ func (d Decimal) fillBuffer(buf []byte, trimTrailingZeros bool) int {
q, r := quoRem64(quo, 10)
n++

// nolint: gosec
buf[l-n] = uint8(r%10) + '0'
buf[l-n] = byte(r%10) + '0'
if q.IsZero() {
break
}
Expand Down
22 changes: 10 additions & 12 deletions decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ var (
ErrEmptyString = fmt.Errorf("parse empty string")

// ErrMaxStrLen is returned when the input string exceeds the maximum length
// This limitation is set to prevent large string input which can cause performance issue
// Maximum length is set to 200
// Maximum length is arbitrarily set to 200 so string length value can fit in 1 byte (for MarshalBinary).
// Also such that big number (more than 200 digits) is unrealistic in financial system
// which this library is mainly designed for.
ErrMaxStrLen = fmt.Errorf("string input exceeds maximum length %d", maxStrLen)

// ErrInvalidFormat is returned when the input string is not in the correct format
Expand Down Expand Up @@ -213,7 +214,7 @@ func NewFromInt64(coef int64, prec uint8) (Decimal, error) {
return Decimal{}, ErrPrecOutOfRange
}

// nolint: gosec
//nolint:gosec
return newDecimal(neg, bintFromU64(uint64(coef)), prec), nil
}

Expand Down Expand Up @@ -278,6 +279,7 @@ func (d Decimal) InexactFloat64() float64 {
// Returns error if:
// 1. empty/invalid string
// 2. the number has more than 19 digits after the decimal point
// 3. string length exceeds maxStrLen (which is 200 characters. See [ErrMaxStrLen] for more details)
func Parse(s string) (Decimal, error) {
return parseBytes(unsafeStringToBytes(s))
}
Expand Down Expand Up @@ -429,10 +431,6 @@ func (d Decimal) Sub64(e uint64) Decimal {
// Mul returns d * e.
// The result will have at most defaultPrec digits after the decimal point.
func (d Decimal) Mul(e Decimal) Decimal {
if e.coef.IsZero() {
return Decimal{}
}

prec := d.prec + e.prec
neg := d.neg != e.neg

Expand Down Expand Up @@ -1149,7 +1147,7 @@ func (d Decimal) PowInt(e int) Decimal {
neg = false
}

// nolint: gosec
//nolint:gosec
return newDecimal(neg, bintFromBigInt(qBig), uint8(powPrecision))
}

Expand Down Expand Up @@ -1215,7 +1213,7 @@ func (d Decimal) tryPowIntU128(e int) (Decimal, error) {
return Decimal{}, errOverflow
}

// nolint: gosec
//nolint:gosec
return newDecimal(neg, bintFromU128(u128{hi: result.hi, lo: result.lo}), uint8(powPrecision)), nil
}

Expand Down Expand Up @@ -1269,7 +1267,7 @@ func (d Decimal) tryInversePowIntU128(e int) (Decimal, error) {
return Decimal{}, errOverflow
}

// nolint: gosec
//nolint:gosec
a256 := one128.MulToU256(pow10[defaultPrec+uint8(powPrecision)])

q, err := a256.fastQuo(u128{hi: result.hi, lo: result.lo})
Expand All @@ -1288,7 +1286,7 @@ func (d Decimal) tryInversePowIntU128(e int) (Decimal, error) {
}

// a256 = 10^(powPrecision + factor + defaultPrec)
// nolint: gosec
//nolint:gosec
a256 := pow10[factor].MulToU256(pow10[defaultPrec+uint8(powPrecision)])
q, err := a256.fastQuo(u128{hi: result.hi, lo: result.lo})
if err != nil {
Expand Down Expand Up @@ -1341,7 +1339,7 @@ func (d Decimal) sqrtU128() (Decimal, error) {
return Decimal{}, errOverflow
}

// nolint: gosec
//nolint:gosec
bitLen := uint(coef.bitLen()) // bitLen < 192

// initial guess = 2^((bitLen + 1) / 2) ≥ √coef
Expand Down
10 changes: 0 additions & 10 deletions decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,6 @@ func TestAdd(t *testing.T) {
aa := decimal.RequireFromString(tc.a)
bb := decimal.RequireFromString(tc.b)

// nolint: gosec
prec := int32(c.Prec())
cc := aa.Add(bb).Truncate(prec)

Expand Down Expand Up @@ -2339,12 +2338,3 @@ func TestPrecUint(t *testing.T) {
})
}
}

func BenchmarkDiv(b *testing.B) {
a := MustParse("22773757910726981402256170801141121114")
bb := MustParse("811656739243220271.159")

for i := 0; i < b.N; i++ {
_, _ = a.Div(bb)
}
}
8 changes: 7 additions & 1 deletion u128.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ func (u u128) Mul(v u128) (u128, error) {
// MulToU256 returns u*v and carry.
// The whole result will be stored in a 256-bits unsigned integer.
func (u u128) MulToU256(v u128) u256 {
// short path for small numbers
if u.hi == 0 && v.hi == 0 {
hi, lo := bits.Mul64(u.lo, v.lo)
return u256{lo: lo, hi: hi}
}

hi, lo := bits.Mul64(u.lo, v.lo)
p0, p1 := bits.Mul64(u.hi, v.lo)
p2, p3 := bits.Mul64(u.lo, v.hi)
Expand Down Expand Up @@ -183,7 +189,7 @@ func (u u128) QuoRem(v u128) (q, r u128, err error) {
// generate a "trial quotient," guaranteed to be within 1 of the actual
// quotient, then adjust.

// nolint: gosec
//nolint:gosec
n := uint(bits.LeadingZeros64(v.hi))
v1 := v.Lsh(n)
u1 := u.Rsh(1)
Expand Down
2 changes: 1 addition & 1 deletion u256.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (u u256) div256by128(v u128) u128 {
// normalize v
n := bits.LeadingZeros64(v.hi)

// nolint: gosec
//nolint:gosec
v = v.Lsh(uint(n))

// shift u to the left by n bits (n < 64)
Expand Down

0 comments on commit 1d8c752

Please sign in to comment.