diff --git a/arith_dec.go b/arith_dec.go index 3b4e815..44d91ec 100644 --- a/arith_dec.go +++ b/arith_dec.go @@ -17,9 +17,9 @@ var maxDigits = [...]uint{ 15, 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, } -// decDigits returns the minimum number of decimal digits required to represent x; -// the result is 0 for x == 0. -func decDigits(x uint) uint { +// mag returns the magnitude of x such that 10**(mag-1) <= x < 10**mag. +// Returns 0 for x == 0. +func mag(x uint) uint { d := maxDigits[bits.Len(x)] if x < pow10(d-1) { d-- @@ -27,6 +27,18 @@ func decDigits(x uint) uint { return d } +// shl10VU sets z to x*(10**s), s < _WD +func shl10VU(z, x dec, s uint) (r Word) { + m := pow10(s) + for i := 0; i < len(z) && i < len(x); i++ { + h, l := bits.Mul(uint(x[i]), m) + h, l = bits.Div(h, l, _BD) + z[i] = Word(l) + r + r = Word(h) + } + return r +} + // shr10VU sets z to x/(10**s) func shr10VU(z, x dec, s uint) (r Word) { d, m := Word(pow10(s)), Word(pow10(_WD-s)) diff --git a/dec.go b/dec.go index 9636c68..ea73717 100644 --- a/dec.go +++ b/dec.go @@ -30,54 +30,37 @@ const ( // representation of 0 is the empty or nil slice (length = 0). type dec []Word -// norm normalizes dec z by multiplying it by 10 until the most significant -// digit is >= 1. -// -// Returns z with trailing zeros truncated and right shifted (in 10 base) such -// that z % 10 = 0. Returns z and the shift amount. +// 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) { - // truncate leading zero words - z = z[:z.mszw()] - if len(z) == 0 { - return z, 0 - } - - // find lowest non-zero word - exp := uint(0) - i := 0 - w := Word(0) - for i, w = range z { - if w != 0 { - break - } - exp += _WD - } - if debugDecimal && i == len(z) { - panic("BUG: no non-zero word found") + var ls uint + // find first non-zero word + i := len(z) + for i > 0 && z[i-1] == 0 { + i-- + ls += _WD } - - // truncate - if i > 0 { - copy(z, z[i:]) - z = z[:len(z)-i] + z = z[:i] + if len(z) == 0 { + return z, ls } - // partial shift - e := uint(0) - for x := w; x%10 == 0; x /= 10 { - e++ - } - if e != 0 { - exp += e - r := shr10VU(z, z, e) - if z[len(z)-1] == 0 { - z = z[:len(z)-1] - } + if s := _WD - mag(uint(z[len(z)-1])); s != 0 { + ls += s + r := shl10VU(z, z, s) if debugDecimal && r != 0 { - panic("remainder != 0 after norm") + 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 } } - return z, exp + return z, ls } // shr10 shifts z right by s decimal places. Returns @@ -111,23 +94,21 @@ func (x dec) digit(n uint) uint { return (uint(x[n]) / pow10(m)) % 10 } -// mszw returns the index of the most significant zero-word -// such that x === x[:x.mszw()]. -func (x dec) mszw() uint { - i := uint(len(x)) - for i != 0 && x[i-1] == 0 { - i-- - } - if i == 0 { - return uint(len(x)) - } - return i -} - func (x dec) digits() uint { - for msw := len(x) - 1; msw >= 0; msw-- { - if x[msw] != 0 { - return uint(msw)*_WD + decDigits(uint(x[msw])) + // const H = 9 + // const P = 1000000000 + for i, w := range x { + if w != 0 { + // TODO(db47h): is there a way to optimize this? + var d uint + // if w%P == 0 { + // w /= P + // d += H + // } + for ; w%10 != 0; w /= 10 { + d++ + } + return uint(len(x)-i)*_WD - d } } return 0 diff --git a/dec_test.go b/dec_test.go index a5af1ab..237fcdb 100644 --- a/dec_test.go +++ b/dec_test.go @@ -11,24 +11,26 @@ import ( func Test_decNorm(t *testing.T) { rand.Seed(time.Now().UnixNano()) for i := 0; i < 10000; i++ { - again: w := uint(rand.Uint64()) % _BD - if w%10 == 0 { - goto again - } e := uint(rand.Intn(_WD + 1)) h, l := bits.Mul(w, pow10(e)) // convert h, l from base _B (2**64) to base _BD (10**19) or 2**32 -> 10**9 h, l = bits.Div(h, l, _BD) - d, s := dec{Word(l), Word(h)}.norm() - if len(d) > 1 || d[0] != Word(w) || s != e { - t.Fatalf("%ve%v => dec{%v, %v}.norm() = %v, %v --- Expected [%d], %d", - w, e, l, h, d, s, w, e) + d, s := dec{0, Word(l), Word(h), 0}.norm() + // d should now have a single element with e shifted left + ew := w * pow10(_WD-mag(w)) + // expected shift + // _WD : _WD : _WD : ... + // _WD : S + mag(w) + e : ... + es := _WD*2 - (mag(w) + e) + _WD + if len(d) > 1 || d[0] != Word(ew) || s != es { + t.Fatalf("%ve%v => dec{0, %v, %v, 0}.norm() = %v, %v --- Expected [%d], %d", + w, e, l, h, d, s, w, es) } } } -func Test_decDigits(t *testing.T) { +func Test_mag(t *testing.T) { rand.Seed(time.Now().UnixNano()) for i := 0; i < 10000; i++ { n := uint(rand.Uint64()) @@ -36,18 +38,12 @@ func Test_decDigits(t *testing.T) { for m := n; m != 0; m /= 10 { d++ } - if dd := decDigits(n); dd != d { - t.Fatalf("decDigits(%d) = %d, expected %d", n, dd, d) + if dd := mag(n); dd != d { + t.Fatalf("mag(%d) = %d, expected %d", n, dd, d) } } } -func Test_decShr10(t *testing.T) { - d := dec{1234, 0, 1234567890123456789} - d, r, tnz := d.shr10(20) - t.Log(d, r, tnz) -} - func Test_decSetInt(t *testing.T) { b, _ := new(big.Int).SetString("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", 0) prec := uint32(float64(b.BitLen())/ln2_10) + 1 @@ -63,24 +59,33 @@ var ( func Benchmark_decNorm(b *testing.B) { rand.Seed(0xdeadbeefbadf00d) + d := dec{}.make(2)[:0] for i := 0; i < b.N; i++ { w := uint(rand.Uint64()) % _BD e := uint(rand.Intn(_WD)) h, l := w/pow10(_WD-e), (w%pow10(_WD-e))*pow10(e) - benchD, benchU = dec{Word(l), Word(h)}.norm() + d = d.make(2) + d[0], d[1] = Word(l), Word(h) + benchD, benchU = d.norm() } } -func Benchmark_decDigits(b *testing.B) { +func Benchmark_mag(b *testing.B) { rand.Seed(0xdeadbeefbadf00d) for i := 0; i < b.N; i++ { - benchU = decDigits(uint(rand.Uint64()) % _BD) + benchU = mag(uint(rand.Uint64()) % _BD) } } -func BenchmarkDecDigits(b *testing.B) { +func Benchmark_decDigits(b *testing.B) { rand.Seed(0xdeadbeefbadf00d) + d := dec{}.make(1) for i := 0; i < b.N; i++ { - benchU = dec{Word(rand.Uint64()) % _BD}.digits() + w := uint(rand.Uint64()) % _BD + e := uint(rand.Intn(_WD)) + h, l := bits.Mul(w, pow10(e)) + _, l = bits.Div(h, l, _BD) + d[0] = Word(l) + benchU = d.digits() } } diff --git a/decimal_test.go b/decimal_test.go index d10278d..1b8584b 100644 --- a/decimal_test.go +++ b/decimal_test.go @@ -1,13 +1,12 @@ package decimal import ( - "math/big" "testing" ) func TestDecimal_SetInt(t *testing.T) { - b, _ := new(big.Int).SetString("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", 0) - d := new(Decimal) - d.SetInt(b) - t.Log(d.prec, d.dig, d.exp) + // b, _ := new(big.Int).SetString("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", 0) + // d := new(Decimal) + // d.SetInt(b) + // t.Log(d.prec, d.dig, d.exp) }