Skip to content

Commit

Permalink
WIP: left shift mantissae
Browse files Browse the repository at this point in the history
  • Loading branch information
db47h committed May 2, 2020
1 parent 869286d commit 5c6a4c2
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 87 deletions.
18 changes: 15 additions & 3 deletions arith_dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,28 @@ 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--
}
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))
Expand Down
95 changes: 38 additions & 57 deletions dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
49 changes: 27 additions & 22 deletions dec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,39 @@ 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())
d := uint(0)
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
Expand All @@ -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()
}
}
9 changes: 4 additions & 5 deletions decimal_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 5c6a4c2

Please sign in to comment.