Skip to content

Commit 1d8c752

Browse files
authored
fix: magic number in parsing (#18)
1 parent 77b7f6e commit 1d8c752

File tree

7 files changed

+41
-49
lines changed

7 files changed

+41
-49
lines changed

.golangci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ run:
33

44
linters:
55
enable:
6+
- nolintlint
67
- errcheck
78
- gosimple
89
- goimports

bint.go

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package udecimal
22

33
import (
4+
"bytes"
45
"fmt"
56
"math/big"
67
"strings"
78
)
89

10+
const (
11+
// maxDigitU64 is the maximum digits of a number
12+
// that can be safely stored in a uint64.
13+
maxDigitU64 = 19
14+
)
15+
916
var (
1017
bigZero = big.NewInt(0)
1118
bigOne = big.NewInt(1)
@@ -171,7 +178,7 @@ func parseBint(s []byte) (bool, bint, uint8, error) {
171178
return false, bint{}, 0, errInvalidFormat(s)
172179
}
173180

174-
// nolint: gosec
181+
//nolint:gosec
175182
return neg, bintFromBigInt(dValue), uint8(prec), nil
176183
}
177184

@@ -211,7 +218,7 @@ func parseBintFromU128(s []byte) (bool, bint, uint8, error) {
211218
prec uint8
212219
)
213220

214-
if len(s[pos:]) <= 19 {
221+
if len(s[pos:]) <= maxDigitU64 {
215222
coef, prec, err = parseSmallToU128(s[pos:])
216223
} else {
217224
coef, prec, err = parseLargeToU128(s[pos:])
@@ -237,7 +244,7 @@ func parseSmallToU128(s []byte) (u128, uint8, error) {
237244
return u128{}, 0, ErrInvalidFormat
238245
}
239246

240-
// nolint: gosec
247+
//nolint:gosec
241248
prec = uint8(len(s) - i - 1)
242249

243250
// prevent "123." or "-123."
@@ -269,27 +276,12 @@ func parseSmallToU128(s []byte) (u128, uint8, error) {
269276
func parseLargeToU128(s []byte) (u128, uint8, error) {
270277
// find '.' position
271278
l := len(s)
272-
pos := -1
273-
274-
for i := 0; i < len(s); i++ {
275-
if s[i] == '.' {
276-
pos = i
277-
}
278-
}
279-
279+
pos := bytes.IndexByte(s, '.')
280280
if pos == 0 || pos == l-1 {
281+
// prevent ".123" or "123."
281282
return u128{}, 0, ErrInvalidFormat
282283
}
283284

284-
var prec uint8
285-
if pos != -1 {
286-
// nolint: gosec
287-
prec = uint8(l - pos - 1)
288-
if prec > defaultPrec {
289-
return u128{}, 0, ErrPrecOutOfRange
290-
}
291-
}
292-
293285
if pos == -1 {
294286
// no decimal point
295287
coef, err := digitToU128(s)
@@ -300,14 +292,21 @@ func parseLargeToU128(s []byte) (u128, uint8, error) {
300292
return coef, 0, nil
301293
}
302294

295+
// now 0 < pos < l-1
296+
//nolint:gosec
297+
prec := uint8(l - pos - 1)
298+
if prec > defaultPrec {
299+
return u128{}, 0, ErrPrecOutOfRange
300+
}
301+
303302
// number has a decimal point, split into 2 parts: integer and fraction
304303
intPart, err := digitToU128(s[:pos])
305304
if err != nil {
306305
return u128{}, 0, err
307306
}
308307

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

330329
func digitToU128(s []byte) (u128, error) {
331-
if len(s) <= 19 {
330+
if len(s) <= maxDigitU64 {
332331
var u uint64
333332
for i := 0; i < len(s); i++ {
334333
if s[i] < '0' || s[i] > '9' {

codec.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ func (d Decimal) fillBuffer(buf []byte, trimTrailingZeros bool) int {
166166
for ; rem != 0; rem /= 10 {
167167
n++
168168

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

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

191-
// nolint: gosec
192-
buf[l-n] = uint8(r%10) + '0'
190+
buf[l-n] = byte(r%10) + '0'
193191
if q.IsZero() {
194192
break
195193
}

decimal.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ var (
9999
ErrEmptyString = fmt.Errorf("parse empty string")
100100

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

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

216-
// nolint: gosec
217+
//nolint:gosec
217218
return newDecimal(neg, bintFromU64(uint64(coef)), prec), nil
218219
}
219220

@@ -278,6 +279,7 @@ func (d Decimal) InexactFloat64() float64 {
278279
// Returns error if:
279280
// 1. empty/invalid string
280281
// 2. the number has more than 19 digits after the decimal point
282+
// 3. string length exceeds maxStrLen (which is 200 characters. See [ErrMaxStrLen] for more details)
281283
func Parse(s string) (Decimal, error) {
282284
return parseBytes(unsafeStringToBytes(s))
283285
}
@@ -429,10 +431,6 @@ func (d Decimal) Sub64(e uint64) Decimal {
429431
// Mul returns d * e.
430432
// The result will have at most defaultPrec digits after the decimal point.
431433
func (d Decimal) Mul(e Decimal) Decimal {
432-
if e.coef.IsZero() {
433-
return Decimal{}
434-
}
435-
436434
prec := d.prec + e.prec
437435
neg := d.neg != e.neg
438436

@@ -1149,7 +1147,7 @@ func (d Decimal) PowInt(e int) Decimal {
11491147
neg = false
11501148
}
11511149

1152-
// nolint: gosec
1150+
//nolint:gosec
11531151
return newDecimal(neg, bintFromBigInt(qBig), uint8(powPrecision))
11541152
}
11551153

@@ -1215,7 +1213,7 @@ func (d Decimal) tryPowIntU128(e int) (Decimal, error) {
12151213
return Decimal{}, errOverflow
12161214
}
12171215

1218-
// nolint: gosec
1216+
//nolint:gosec
12191217
return newDecimal(neg, bintFromU128(u128{hi: result.hi, lo: result.lo}), uint8(powPrecision)), nil
12201218
}
12211219

@@ -1269,7 +1267,7 @@ func (d Decimal) tryInversePowIntU128(e int) (Decimal, error) {
12691267
return Decimal{}, errOverflow
12701268
}
12711269

1272-
// nolint: gosec
1270+
//nolint:gosec
12731271
a256 := one128.MulToU256(pow10[defaultPrec+uint8(powPrecision)])
12741272

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

12901288
// a256 = 10^(powPrecision + factor + defaultPrec)
1291-
// nolint: gosec
1289+
//nolint:gosec
12921290
a256 := pow10[factor].MulToU256(pow10[defaultPrec+uint8(powPrecision)])
12931291
q, err := a256.fastQuo(u128{hi: result.hi, lo: result.lo})
12941292
if err != nil {
@@ -1341,7 +1339,7 @@ func (d Decimal) sqrtU128() (Decimal, error) {
13411339
return Decimal{}, errOverflow
13421340
}
13431341

1344-
// nolint: gosec
1342+
//nolint:gosec
13451343
bitLen := uint(coef.bitLen()) // bitLen < 192
13461344

13471345
// initial guess = 2^((bitLen + 1) / 2) ≥ √coef

decimal_test.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,6 @@ func TestAdd(t *testing.T) {
564564
aa := decimal.RequireFromString(tc.a)
565565
bb := decimal.RequireFromString(tc.b)
566566

567-
// nolint: gosec
568567
prec := int32(c.Prec())
569568
cc := aa.Add(bb).Truncate(prec)
570569

@@ -2339,12 +2338,3 @@ func TestPrecUint(t *testing.T) {
23392338
})
23402339
}
23412340
}
2342-
2343-
func BenchmarkDiv(b *testing.B) {
2344-
a := MustParse("22773757910726981402256170801141121114")
2345-
bb := MustParse("811656739243220271.159")
2346-
2347-
for i := 0; i < b.N; i++ {
2348-
_, _ = a.Div(bb)
2349-
}
2350-
}

u128.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ func (u u128) Mul(v u128) (u128, error) {
137137
// MulToU256 returns u*v and carry.
138138
// The whole result will be stored in a 256-bits unsigned integer.
139139
func (u u128) MulToU256(v u128) u256 {
140+
// short path for small numbers
141+
if u.hi == 0 && v.hi == 0 {
142+
hi, lo := bits.Mul64(u.lo, v.lo)
143+
return u256{lo: lo, hi: hi}
144+
}
145+
140146
hi, lo := bits.Mul64(u.lo, v.lo)
141147
p0, p1 := bits.Mul64(u.hi, v.lo)
142148
p2, p3 := bits.Mul64(u.lo, v.hi)
@@ -183,7 +189,7 @@ func (u u128) QuoRem(v u128) (q, r u128, err error) {
183189
// generate a "trial quotient," guaranteed to be within 1 of the actual
184190
// quotient, then adjust.
185191

186-
// nolint: gosec
192+
//nolint:gosec
187193
n := uint(bits.LeadingZeros64(v.hi))
188194
v1 := v.Lsh(n)
189195
u1 := u.Rsh(1)

u256.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func (u u256) div256by128(v u128) u128 {
165165
// normalize v
166166
n := bits.LeadingZeros64(v.hi)
167167

168-
// nolint: gosec
168+
//nolint:gosec
169169
v = v.Lsh(uint(n))
170170

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

0 commit comments

Comments
 (0)