Skip to content

Commit

Permalink
dec: split norm() into dec.norm() and dnorm(dec)
Browse files Browse the repository at this point in the history
Like big.Float, the least significant zeros of the mantissa are no more
trimmed.
  • Loading branch information
db47h committed May 4, 2020
1 parent 8b88c09 commit f96bd0c
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 167 deletions.
64 changes: 63 additions & 1 deletion arith_dec.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package decimal

import "math/bits"
import (
"math/bits"
)

var pow10s = [...]uint64{
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000,
Expand Down Expand Up @@ -102,6 +104,39 @@ func dec64TrailingZeros(n uint64) uint {
return d
}

var decMaxPow32 = [...]uint32{
0, 0, 0, 0,
536870912, 29, 387420489, 18, 268435456, 14, 244140625, 12, 362797056, 11,
282475249, 10, 134217728, 9, 387420489, 9, 1000000000, 9, 214358881, 8,
429981696, 8, 815730721, 8, 105413504, 7, 170859375, 7, 268435456, 7,
410338673, 7, 612220032, 7, 893871739, 7, 64000000, 6, 85766121, 6,
113379904, 6, 148035889, 6, 191102976, 6, 244140625, 6, 308915776, 6,
387420489, 6, 481890304, 6, 594823321, 6, 729000000, 6, 887503681, 6,
33554432, 5, 39135393, 5, 45435424, 5, 52521875, 5, 60466176, 5,
}

var decMaxPow64 = [...]uint64{
0, 0, 0, 0,
9223372036854775808, 63, 4052555153018976267, 39, 4611686018427387904, 31, 7450580596923828125, 27, 9983543956220149760, 25,
8922003266371364727, 23, 9223372036854775808, 21, 1350851717672992089, 19, 10000000000000000000, 19, 8667208279016151025, 20,
8176589207175692288, 18, 8650415919381337933, 17, 2177953337809371136, 16, 6568408355712890625, 16, 1152921504606846976, 15,
2862423051509815793, 15, 6746640616477458432, 15, 799006685782884121, 14, 1638400000000000000, 14, 3243919932521508681, 14,
7752859499445190656, 15, 504036361936467383, 13, 6795192965888212992, 15, 1490116119384765625, 13, 9169742482168496128, 14,
4052555153018976267, 13, 6502111422497947648, 13, 353814783205469041, 12, 531441000000000000, 12, 5970802223735490975, 13,
1152921504606846976, 12, 1667889514952984961, 12, 7351326950727229440, 13, 7592253339725112179, 13, 4738381338321616896, 12,
}

// decMaxPow returns (b**n, n) such that b**n is the largest power b**n such that (b**n) <= _BD.
// For instance decMaxPow(10) == (1e19 - 1, 19) for 19 decimal digits in a 64bit Word.
// In other words, at most n digits in base b fit into a decimal Word.
func decMaxPow(b uint) (p uint, n int) {
i := b / 2
if bits.UintSize == 32 {
return uint(decMaxPow32[i]), int(decMaxPow32[i+1])
}
return uint(decMaxPow64[i]), int(decMaxPow64[i+1])
}

// addW adds y to x. The resulting carry c is either 0 or 1.
func add10VW(z, x dec, y Word) (c Word) {
s := x[0] + y
Expand All @@ -128,3 +163,30 @@ func add10VW(z, x dec, y Word) (c Word) {
}
return c
}

func div10WVW(z []Word, xn Word, x []Word, y Word) (r Word) {
r = xn
for i := len(z) - 1; i >= 0; i-- {
h, l := mulAddWWW(r, _BD, x[i])
z[i], r = divWW(h, l, y)
}
return
}

func mulAdd10VWW(z, x []Word, y, r Word) (c Word) {
c = r
// The comment near the top of this file discusses this for loop condition.
for i := 0; i < len(z) && i < len(x); i++ {
c, z[i] = mulAdd10WWW(x[i], y, c)
}
return
}

// z1*_BD + z0 = x*y + c
func mulAdd10WWW(x, y, c Word) (z1, z0 Word) {
hi, lo := bits.Mul(uint(x), uint(y))
var cc uint
lo, cc = bits.Add(lo, uint(c), 0)
hi, lo = bits.Div(hi+cc, lo, _BD)
return Word(hi), Word(lo)
}
161 changes: 96 additions & 65 deletions dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,65 +31,21 @@ const (
// representation of 0 is the empty or nil slice (length = 0).
type dec []Word

// Returns z with leading zeros truncated and left shifted (in 10 base) such
// that the most significant digit is >= 1. Returns z and the left shift amount.
func (z dec) norm() (dec, uint) {
var ls uint
// find first non-zero word
i := len(z)
for i > 0 && z[i-1] == 0 {
i--
ls += _WD
}
z = z[:i]
if len(z) == 0 {
return z, 0
}
// partial shift
if s := _WD - mag(uint(z[len(z)-1])); s != 0 {
ls += s
r := shl10VU(z, z, s)
if debugDecimal && r != 0 {
panic("shl10VU returned non zero carry")
}
}
// remove trailing zeros
for i, w := range z {
if w != 0 {
copy(z, z[i:])
z = z[:len(z)-i]
break
}
func (z dec) clear() {
for i := range z {
z[i] = 0
}
return z, ls
}

// shr10 shifts z right by s decimal places. Returns
// z and the most significant digit removed and a boolean
// indicating if there were any non-zero digits following r
func (z dec) shr10(s uint) (d dec, r Word, tnz bool) {
nw, s := s/_WD, s%_WD
if nw > 0 {
// save rounding digit
r = z[nw-1]
for _, w := range z[:nw-1] {
tnz = tnz || w != 0
}
copy(z, z[nw:])
z = z[:len(z)-int(nw)]
func (z dec) norm() dec {
i := len(z)
for i > 0 && z[i-1] == 0 {
i--
}
if s == 0 {
r, m := r/(_BD-1), r%(_BD-1)
return z, r, m != 0
}
tnz = tnz || r != 0
// shift right by 0 < s < _WD
r = shr10VU(z, z, s)
p := Word(pow10(s - 1))
r, m := r/p, r%p
return z, r, tnz || m != 0
return z[0:i]
}

// TODO(db47h): change this to retun # of trailing zeroes.
func (x dec) digits() uint {
for i, w := range x {
if w != 0 {
Expand All @@ -108,12 +64,6 @@ func (x dec) digit(i uint) uint {
return (uint(x[j]) / pow10(i)) % 10
}

func (z dec) set(x dec) dec {
z = z.make(len(x))
copy(z, x)
return z
}

func (z dec) make(n int) dec {
if n <= cap(z) {
return z[:n] // reuse z
Expand All @@ -128,17 +78,40 @@ func (z dec) make(n int) dec {
return make(dec, n, n+e)
}

func (z dec) set(x dec) dec {
z = z.make(len(x))
copy(z, x)
return z
}

func (z dec) setWord(x Word) dec {
if x == 0 {
return z[:0]
}
z = z.make(1)
z[0] = x
return z
}

// setInt sets z such that z*10**exp = x with 0 < z <= 1.
// Returns z and exp.
func (z dec) setInt(x *big.Int) (dec, uint) {
b := new(big.Int).Set(x).Bits()
bb := x.Bits()
// TODO(db47h): here we cannot directly copy(b, bb)
b := make([]Word, len(bb))
for i := 0; i < len(b) && i < len(bb); i++ {
b[i] = Word(bb[i])
}
var i int
for i = 0; i < len(z) && len(b) > 0; i++ {
z[i] = Word(divWVW(b, 0, b, big.Word(_BD)))
for i = 0; i < len(z); i++ {
z[i] = Word(divWVW(b, 0, b, _BD))
}
z = z[:i]
z, s := z.norm()
return z, uint(i)*_WD - s
z = z.norm()
if len(z) == 0 {
return z, 0
}
s := dnorm(z)
return z, uint(len(z))*_WD - uint(s)
}

// sticky returns 1 if there's a non zero digit within the
Expand All @@ -163,6 +136,64 @@ func (x dec) sticky(i uint) uint {
return 0
}

// q = (x-r)/y, with 0 <= r < y
func (z dec) divW(x dec, y Word) (q dec, r Word) {
m := len(x)
switch {
case y == 0:
panic("division by zero")
case y == 1:
q = z.set(x) // result is x
return
case m == 0:
q = z[:0] // result is 0
return
}
// m > 0
z = z.make(m)
r = div10WVW(z, 0, x, y)
q = z.norm()
return
}

func (z dec) mulAddWW(x dec, y, r Word) dec {
m := len(x)
if m == 0 || y == 0 {
return z.setWord(r) // result is r
}
// m > 0

z = z.make(m + 1)
z[m] = mulAdd10VWW(z[0:m], x, y, r)

return z.norm()
}

// z = x * 10**s
func (z dec) shl(x dec, s uint) dec {
if s == 0 {
if same(z, x) {
return z
}
if !alias(z, x) {
return z.set(x)
}
}

m := len(x)
if m == 0 {
return z[:0]
}
// m > 0

n := m + int(s/_WD)
z = z.make(n + 1)
z[n] = shl10VU(z[n-m:n], x, s%_WD)
z[0 : n-m].clear()

return z.norm()
}

// getDec returns a *dec of len n. The contents may not be zero.
// The pool holds *dec to avoid allocation when converting to interface{}.
func getDec(n int) *dec {
Expand Down
Loading

0 comments on commit f96bd0c

Please sign in to comment.