Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions bint.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
return false, bint{}, 0, errInvalidFormat(s)
}

//nolint:gosec // prec <= maxPrec (19) and can be safely converted to uint8

Check failure on line 204 in bint.go

View workflow job for this annotation

GitHub Actions / lint

directive `//nolint:gosec // prec <= maxPrec (19) and can be safely converted to uint8` is unused for linter "gosec" (nolintlint)
return neg, bintFromBigInt(dValue), uint8(prec), nil
}

Expand Down Expand Up @@ -373,26 +373,40 @@
return u128{lo: u}, nil
}

// number is too large
// number is too large to fit into uint64, parse it in chunks of up to 19 digits.
// Each chunk is accumulated with cheap uint64 arithmetic, so only 1 u128 mul/add
// is required per chunk instead of per digit.
var (
u u128
err error
)

for i := 0; i < len(s); i++ {
if s[i] < '0' || s[i] > '9' {
return u128{}, ErrInvalidFormat
for len(s) > 0 {
n := len(s)
if n > maxDigitU64 {
n = maxDigitU64
}

u, err = u.Mul64(10)
var chunk uint64
for i := 0; i < n; i++ {
if s[i] < '0' || s[i] > '9' {
return u128{}, ErrInvalidFormat
}

chunk = chunk*10 + uint64(s[i]-'0')
}

u, err = u.Mul64(pow10[n].lo)
if err != nil {
return u128{}, err
}

u, err = u.Add64(uint64(s[i] - '0'))
u, err = u.Add64(chunk)
if err != nil {
return u128{}, err
}

s = s[n:]
}

return u, nil
Expand Down
74 changes: 52 additions & 22 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,24 +238,7 @@
buf[n] = '0'
n--
} else {
for quo.Cmp64(100) >= 0 {
q, r := quoRem64(quo, 100)
r = r * 2
quo = q

buf[n] = table[r+1]
buf[n-1] = table[r]
n -= 2
}

if quo.Cmp64(10) >= 0 {
buf[n] = table[quo.lo*2+1]
buf[n-1] = table[quo.lo*2]
n -= 2
} else {
buf[n] = byte(quo.lo) + '0'
n--
}
n = appendU128(buf[:], n, quo)
}

if d.neg {
Expand All @@ -274,12 +257,59 @@
return append(inBuf, result...)
}

func quoRem64(u u128, v uint64) (q u128, r uint64) {
if u.hi == 0 {
return u128{lo: u.lo / v}, u.lo % v
// appendU128 writes the decimal digits of quo into buf from right to left,
// starting at index n, and returns the index right before the leftmost digit.
// It first reduces the u128 to uint64 chunks of 19 digits (at most 2 u128 divisions),
// then formats each chunk using only 64-bit arithmetic.
func appendU128(buf []byte, n int, quo u128) int {
// reduce quo to uint64 chunks of 19 digits each,
// the loop runs at most twice as quo has at most 39 digits
var chunks [2]uint64
nc := 0
for quo.hi != 0 {
var r uint64
quo, r = quo.QuoRem64(1e19)
chunks[nc] = r
nc++
}
last := quo.lo

// low chunks: always print all 19 digits, zero-padded
for c := 0; c < nc; c++ {
rem := chunks[c]
for j := 0; j < 9; j++ {
r := rem % 100 * 2
rem /= 100
buf[n] = table[r+1]
buf[n-1] = table[r]
n -= 2
}

// 19th (leftmost) digit of the chunk
//nolint:gosec // rem < 10 after dividing out 18 digits, can be safely converted to byte
buf[n] = byte(rem) + '0'
n--
}

// highest chunk: no zero padding
for last >= 100 {
r := last % 100 * 2
last /= 100
buf[n] = table[r+1]
buf[n-1] = table[r]
n -= 2
}

if last >= 10 {
buf[n] = table[last*2+1]
buf[n-1] = table[last*2]
n -= 2
} else {
buf[n] = byte(last) + '0'
n--
}

return u.QuoRem64(v)
return n
}

func unsafeStringToBytes(s string) []byte {
Expand Down Expand Up @@ -413,9 +443,9 @@
buf := make([]byte, totalBytes)

// overflow + neg with overflow = true (always 1)
buf[0] = byte(1<<4 | neg)

Check failure on line 446 in codec.go

View workflow job for this annotation

GitHub Actions / lint

G115: integer overflow conversion int -> uint8 (gosec)
buf[1] = byte(d.prec)
buf[2] = byte(totalBytes)

Check failure on line 448 in codec.go

View workflow job for this annotation

GitHub Actions / lint

G115: integer overflow conversion int -> uint8 (gosec)
d.coef.bigInt.FillBytes(buf[3:])

return append(input, buf...), nil
Expand Down
8 changes: 6 additions & 2 deletions decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1038,8 +1038,10 @@
return d
}

// factor is a power of 10 with hi == 0 (max is 10^19),
// so half can be computed with a single shift instead of a 128-bit division
factor := pow10[d.prec-prec]
half, _ := factor.QuoRem64(2)
half := u128{lo: factor.lo >> 1}

if !d.coef.overflow() {
var err error
Expand Down Expand Up @@ -1078,8 +1080,10 @@
return d
}

// factor is a power of 10 with hi == 0 (max is 10^19),
// so half can be computed with a single shift instead of a 128-bit division
factor := pow10[d.prec-prec]
half, _ := factor.QuoRem64(2)
half := u128{lo: factor.lo >> 1}

if !d.coef.overflow() {
var err error
Expand Down Expand Up @@ -1681,7 +1685,7 @@
return Decimal{}, errOverflow
}

//nolint:gosec // 0 <= coef.bitLen() < 256, so it's safe to convert to uint

Check failure on line 1688 in decimal.go

View workflow job for this annotation

GitHub Actions / lint

directive `//nolint:gosec // 0 <= coef.bitLen() < 256, so it's safe to convert to uint` is unused for linter "gosec" (nolintlint)
bitLen := uint(coef.bitLen())

// initial guess = 2^((bitLen + 1) / 2) ≥ √coef
Expand Down
6 changes: 6 additions & 0 deletions u128.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@
// generate a "trial quotient," guaranteed to be within 1 of the actual
// quotient, then adjust.

//nolint:gosec // 0 <= bits.LeadingZeros64(v.hi) <= 63, so it's safe to convert to uint

Check failure on line 183 in u128.go

View workflow job for this annotation

GitHub Actions / lint

directive `//nolint:gosec // 0 <= bits.LeadingZeros64(v.hi) <= 63, so it's safe to convert to uint` is unused for linter "gosec" (nolintlint)
n := uint(bits.LeadingZeros64(v.hi))
v1 := v.Lsh(n)
u1 := u.Rsh(1)
Expand Down Expand Up @@ -273,6 +273,12 @@
}

func (u u128) ToBigInt() *big.Int {
if bits.UintSize == 64 {
// fast path for 64-bit platforms: set the words directly,
// avoiding the intermediate byte buffer and its decoding
return new(big.Int).SetBits([]big.Word{big.Word(u.lo), big.Word(u.hi)})
}

bytes := make([]byte, 16)
binary.BigEndian.PutUint64(bytes, u.hi)
binary.BigEndian.PutUint64(bytes[8:], u.lo)
Expand Down
Loading