From 5c6a4c2d63beea9cdc930deee6bd91a8421c7d6e Mon Sep 17 00:00:00 2001
From: Denis Bernard <db047h@gmail.com>
Date: Sat, 2 May 2020 13:58:46 +0200
Subject: [PATCH] WIP: left shift mantissae

---
 arith_dec.go    | 18 ++++++++--
 dec.go          | 95 ++++++++++++++++++++-----------------------------
 dec_test.go     | 49 +++++++++++++------------
 decimal_test.go |  9 +++--
 4 files changed, 84 insertions(+), 87 deletions(-)

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)
 }