Skip to content

Commit

Permalink
Decimal: implement text marshaling & gob encoding
Browse files Browse the repository at this point in the history
db47h committed May 19, 2020
1 parent 246defa commit 200082d
Showing 6 changed files with 225 additions and 43 deletions.
43 changes: 43 additions & 0 deletions dec.go
Original file line number Diff line number Diff line change
@@ -1155,3 +1155,46 @@ func decKaratsuba(z, x, y dec) {
decKaratsubaSub(z[n2:], p, n)
}
}

// bytes writes the value of x into buf using big-endian encoding.
// len(buf) must be >= len(x)*_S. The value of x is encoded in the
// slice buf[i:]. The number i of unused bytes at the beginning of
// buf is returned as result.
func (x dec) bytes(buf []byte) (i int) {
i = len(buf)
for _, d := range x {
for j := 0; j < _S; j++ {
i--
buf[i] = byte(d)
d >>= 8
}
}

for i < len(buf) && buf[i] == 0 {
i++
}

return
}

// setBytes interprets buf as the bytes of a big-endian unsigned
// integer, sets z to that value, and returns z.
func (z dec) setBytes(buf []byte) dec {
z = z.make((len(buf) + _S - 1) / _S)

i := len(buf)
for k := 0; i >= _S; k++ {
z[k] = bigEndianWord(buf[i-_S : i])
i -= _S
}
if i > 0 {
var d Word
for s := uint(0); i > 0; s += 8 {
d |= Word(buf[i-1]) << s
i--
}
z[len(z)-1] = d
}

return z.norm()
}
53 changes: 35 additions & 18 deletions decimal.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package decimal

import (
"encoding"
"encoding/gob"
"fmt"
"math"
"math/big"
@@ -19,8 +21,14 @@ const DefaultDecimalPrec = 34
var decimalZero Decimal

var (
_ fmt.Scanner = &decimalZero // *Decimal must implement fmt.Scanner
_ fmt.Formatter = &decimalZero // *Decimal must implement fmt.Formatter
// required implemented interfaces
_ fmt.Stringer = &decimalZero
_ fmt.Scanner = &decimalZero
_ fmt.Formatter = &decimalZero
_ encoding.TextMarshaler = &decimalZero
_ encoding.TextUnmarshaler = &decimalZero
_ gob.GobEncoder = &decimalZero
_ gob.GobDecoder = &decimalZero
)

type Decimal struct {
@@ -423,6 +431,31 @@ func (x *Decimal) Float(z *big.Float) *big.Float {
return z
}

// pow10 sets z to 10**n and returns z.
// n must not be negative.
func pow10Float(z *big.Float, n uint64) *big.Float {
const m = uint64(len(pow10tab) - 1)
if n <= m {
return z.SetUint64(pow10tab[n])
}
// n > m

z.SetUint64(pow10tab[m])
n -= m

f := new(big.Float).SetPrec(z.Prec() + _W).SetUint64(10)

for n > 0 {
if n&1 != 0 {
z.Mul(z, f)
}
f.Mul(f, f)
n >>= 1
}

return z
}

// Float32 returns the float32 value nearest to x. If x is too small to be
// represented by a float32 (|x| < math.SmallestNonzeroFloat32), the result
// is (0, Below) or (-0, Above), respectively, depending on the sign of x.
@@ -453,14 +486,6 @@ func (x *Decimal) Float64() (float64, Accuracy) {
return f, Accuracy(a)
}

func (z *Decimal) GobDecode(buf []byte) error {
panic("not implemented")
}

func (x *Decimal) GobEncode() ([]byte, error) {
panic("not implemented")
}

// Int returns the result of truncating x towards zero; or nil if x is an
// infinity.
// The result is Exact if x.IsInt(); otherwise it is Below for x > 0, and Above
@@ -632,10 +657,6 @@ func (x *Decimal) MantExp(mant *Decimal) (exp int) {
return
}

func (x *Decimal) MarshalText() (text []byte, err error) {
panic("not implemented")
}

// MinPrec returns the minimum precision required to represent x exactly
// (i.e., the smallest prec before x.SetPrec(prec) would start rounding x).
// The result is 0 for |x| == 0 and |x| == Inf.
@@ -1234,10 +1255,6 @@ func (x *Decimal) Uint64() (uint64, Accuracy) {
panic("unreachable")
}

func (z *Decimal) UnmarshalText(text []byte) error {
panic("not implemented")
}

func (x *Decimal) validate() {
if !debugDecimal {
// avoid performance bugs
120 changes: 120 additions & 0 deletions decimal_marsh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file implements encoding/decoding of Decimals.

package decimal

import (
"encoding/binary"
"fmt"
)

// Gob codec version. Permits backward-compatible changes to the encoding.
const decimalGobVersion byte = 1

// GobEncode implements the gob.GobEncoder interface.
// The Float value and all its attributes (precision,
// rounding mode, accuracy) are marshaled.
func (x *Decimal) GobEncode() ([]byte, error) {
if x == nil {
return nil, nil
}

// determine max. space (bytes) required for encoding
sz := 1 + 1 + 4 // version + mode|acc|form|neg (3+2+2+1bit) + prec
n := 0 // number of mantissa words
if x.form == finite {
// add space for mantissa and exponent
n = int((x.prec + (_DW - 1)) / _DW) // required mantissa length in words for given precision
// actual mantissa slice could be shorter (trailing 0's) or longer (unused bits):
// - if shorter, only encode the words present
// - if longer, cut off unused words when encoding in bytes
// (in practice, this should never happen since rounding
// takes care of it, but be safe and do it always)
if len(x.mant) < n {
n = len(x.mant)
}
// len(x.mant) >= n
sz += 4 + n*_S // exp + mant
}
buf := make([]byte, sz)

buf[0] = decimalGobVersion
b := byte(x.mode&7)<<5 | byte((x.acc+1)&3)<<3 | byte(x.form&3)<<1
if x.neg {
b |= 1
}
buf[1] = b
binary.BigEndian.PutUint32(buf[2:], x.prec)

if x.form == finite {
binary.BigEndian.PutUint32(buf[6:], uint32(x.exp))
x.mant[len(x.mant)-n:].bytes(buf[10:]) // cut off unused trailing words
}

return buf, nil
}

// GobDecode implements the gob.GobDecoder interface.
// The result is rounded per the precision and rounding mode of
// z unless z's precision is 0, in which case z is set exactly
// to the decoded value.
func (z *Decimal) GobDecode(buf []byte) error {
if len(buf) == 0 {
// Other side sent a nil or default value.
*z = Decimal{}
return nil
}

if buf[0] != decimalGobVersion {
return fmt.Errorf("Decimal.GobDecode: encoding version %d not supported", buf[0])
}

oldPrec := z.prec
oldMode := z.mode

b := buf[1]
z.mode = RoundingMode((b >> 5) & 7)
z.acc = Accuracy((b>>3)&3) - 1
z.form = form((b >> 1) & 3)
z.neg = b&1 != 0
z.prec = binary.BigEndian.Uint32(buf[2:])

if z.form == finite {
z.exp = int32(binary.BigEndian.Uint32(buf[6:]))
z.mant = z.mant.setBytes(buf[10:])
}

if oldPrec != 0 {
z.mode = oldMode
z.SetPrec(uint(oldPrec))
}

return nil
}

// MarshalText implements the encoding.TextMarshaler interface.
// Only the Decimal value is marshaled (in full precision), other
// attributes such as precision or accuracy are ignored.
func (x *Decimal) MarshalText() (text []byte, err error) {
if x == nil {
return []byte("<nil>"), nil
}
var buf []byte
return x.Append(buf, 'g', -1), nil
}

// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The result is rounded per the precision and rounding mode of z.
// If z's precision is 0, it is changed to 64 before rounding takes
// effect.
func (z *Decimal) UnmarshalText(text []byte) error {
// TODO(db47h): get rid of the []byte/string conversion
_, _, err := z.Parse(string(text), 0)
if err != nil {
err = fmt.Errorf("math/big: cannot unmarshal %q into a *big.Float (%v)", text, err)
}
return err
}
File renamed without changes.
18 changes: 18 additions & 0 deletions decimal_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package decimal

import (
"math"
"math/big"
"math/rand"
"reflect"
@@ -113,3 +114,20 @@ func BenchmarkDecimal_Sqrt(b *testing.B) {
z.Sqrt(x)
}
}

func TestDecimal_String(t *testing.T) {
z := big.NewFloat(1.1)
t.Logf("z: %g!", z)
// x, _ := new(Decimal).SetPrec(34).SetString("9223372036854775808")
x := new(Decimal).SetPrec(17).SetFloat64(math.MaxFloat64)
// t.Log(x.Text('p', 0))
t.Logf("x: %g!", x)
x.SetPrec(17).SetFloat(z)
t.Logf("z = %g -> x (%d) = %g", z, x.Prec(), x)
x.Float(z)
t.Logf("x = %g -> z = %g", x, z)
x.Sqrt(x.SetPrec(33))
t.Logf("x: %g!", x)
t.Logf("x: %.*f!", x.Prec()-1, x)
t.Log(" 1.41421356237309504880168872420969807856967187537694807317667974")
}
34 changes: 9 additions & 25 deletions stdlib.go
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
package decimal

import (
"encoding/binary"
"errors"
"fmt"
"io"
@@ -280,31 +281,6 @@ func greaterThan(x1, x2, y1, y2 Word) bool {
return x1 > y1 || x1 == y1 && x2 > y2
}

// pow10 sets z to 10**n and returns z.
// n must not be negative.
func pow10Float(z *big.Float, n uint64) *big.Float {
const m = uint64(len(pow10tab) - 1)
if n <= m {
return z.SetUint64(pow10tab[n])
}
// n > m

z.SetUint64(pow10tab[m])
n -= m

f := new(big.Float).SetPrec(z.Prec() + _W).SetUint64(10)

for n > 0 {
if n&1 != 0 {
z.Mul(z, f)
}
f.Mul(f, f)
n >>= 1
}

return z
}

func abs(x int) int {
if x < 0 {
return -x
@@ -342,3 +318,11 @@ func makeNat(z []big.Word, n int) []big.Word {
const e = 4 // extra capacity
return make([]big.Word, n, n+e)
}

// bigEndianWord returns the contents of buf interpreted as a big-endian encoded Word value.
func bigEndianWord(buf []byte) Word {
if _W == 64 {
return Word(binary.BigEndian.Uint64(buf))
}
return Word(binary.BigEndian.Uint32(buf))
}

0 comments on commit 200082d

Please sign in to comment.