diff --git a/big.go b/big.go index d813a8c..440a964 100644 --- a/big.go +++ b/big.go @@ -110,7 +110,7 @@ func (bigIntCodec) nilsLast() Codec[*big.Int] { // the standard library just doesn't expose that information. // A description of the encoding and why it does what it does follows. // -// Shift a copy of the big.Float so that: +// If not infinite or zero, shift a copy of the big.Float so that: // // all significant bits are to the left of the point, // the high bit of the high byte is 1, and @@ -156,7 +156,12 @@ func (bigIntCodec) nilsLast() Codec[*big.Int] { // write prefixNilFirst/Last if value is nil and return immediately // write prefixNonNil // write int8: negInf/negFinite/negZero/posZero/posFinite/posInf -// if infinite or zero, we're done +// if infinite, we're done +// if zero +// write int32 precision +// negate precision first if Float is negative +// write uint8 rounding mode +// we're done // write int32 exponent // negate exponent first if Float is negative // write the (big-endian) bytes of the big.Int of the shifted mantissa @@ -213,7 +218,7 @@ func (c bigFloatCodec) Append(buf []byte, value *big.Float) []byte { shift := computeShift(exp, prec) isInf := value.IsInf() - isZero := prec == 0 + isZero := value.Sign() == 0 var kind int8 switch { @@ -231,9 +236,17 @@ func (c bigFloatCodec) Append(buf []byte, value *big.Float) []byte { kind = posFinite } buf = stdInt8.Append(buf, kind) - if isInf || isZero { + if isInf { return buf } + if isZero { + if signbit { + prec = -prec + } + buf = stdInt32.Append(buf, int32(prec)) + return modeCodec.Append(buf, mode) + } + mantSize := numBytes(prec) start := len(buf) // 9 = 4 (exp) + 4 (prec) + 1 (mode) @@ -269,7 +282,7 @@ func (c bigFloatCodec) Append(buf []byte, value *big.Float) []byte { return buf } -//nolint:cyclop +//nolint:cyclop,funlen func (c bigFloatCodec) Put(buf []byte, value *big.Float) []byte { done, buf := c.prefix.Put(buf, value == nil) if done { @@ -284,7 +297,7 @@ func (c bigFloatCodec) Put(buf []byte, value *big.Float) []byte { shift := computeShift(exp, prec) isInf := value.IsInf() - isZero := prec == 0 + isZero := value.Sign() == 0 var kind int8 switch { @@ -302,11 +315,18 @@ func (c bigFloatCodec) Put(buf []byte, value *big.Float) []byte { kind = posFinite } buf = stdInt8.Put(buf, kind) - if isInf || isZero { + if isInf { return buf } - mantSize := numBytes(prec) + if isZero { + if signbit { + prec = -prec + } + buf = stdInt32.Put(buf, int32(prec)) + return modeCodec.Put(buf, mode) + } + mantSize := numBytes(prec) var tmp big.Float tmp.SetMantExp(value, shift) mantInt, acc := tmp.Int(nil) @@ -343,10 +363,16 @@ func (c bigFloatCodec) Get(buf []byte) (*big.Float, []byte) { return value.SetInf(signbit), buf } if kind == negZero || kind == posZero { + //nolint:govet + prec, buf := stdInt32.Get(buf) + mode, buf := modeCodec.Get(buf) var value big.Float if signbit { + prec = -prec value.Neg(&value) } + value.SetPrec(uint(prec)) + value.SetMode(mode) return &value, buf } diff --git a/big_test.go b/big_test.go index 9c50365..33c4072 100644 --- a/big_test.go +++ b/big_test.go @@ -102,6 +102,19 @@ func newBigFloat64(f float64, shift int, prec uint) *big.Float { return value } +func newBigFloatPosZero(prec uint) *big.Float { + var value big.Float + value.SetPrec(prec) + return &value +} + +func newBigFloatNegZero(prec uint) *big.Float { + var value big.Float + value.SetPrec(prec) + value.Neg(&value) + return &value +} + func newBigFloat(s string) *big.Float { var value big.Float // Parse truncates to 64 bits if precision is currently 0. @@ -114,14 +127,9 @@ func newBigFloat(s string) *big.Float { func TestBigFloat(t *testing.T) { t.Parallel() - var negInf, posInf, negZero, posZero big.Float + var negInf, posInf big.Float negInf.SetInf(true) posInf.SetInf(false) - negZero.Neg(&negZero) - assert.True(t, negZero.Signbit()) - assert.False(t, posZero.Signbit()) - assert.Equal(t, 0, negZero.Cmp(&posZero)) - assert.NotEqual(t, &negZero, &posZero) wholeNumber := newBigFloat(manyDigits + manyDigits) mixedNumber := newBigFloat(manyDigits + "." + manyDigits) @@ -132,12 +140,12 @@ func TestBigFloat(t *testing.T) { testCodec(t, codec, fillTestData(codec, []testCase[*big.Float]{ {"nil", nil, nil}, // example in implementation comments - {"seven(3)", newBigFloat64(7.0, 0, 3), nil}, - {"seven(4)", newBigFloat64(7.0, 0, 4), nil}, - {"seven(10)", newBigFloat64(7.0, 0, 10), nil}, - {"-seven(3)", newBigFloat64(-7.0, 0, 3), nil}, - {"-seven(4)", newBigFloat64(-7.0, 0, 4), nil}, - {"-seven(10)", newBigFloat64(-7.0, 0, 10), nil}, + {"seven (3)", newBigFloat64(7.0, 0, 3), nil}, + {"seven (4)", newBigFloat64(7.0, 0, 4), nil}, + {"seven (10)", newBigFloat64(7.0, 0, 10), nil}, + {"-seven (3)", newBigFloat64(-7.0, 0, 3), nil}, + {"-seven (4)", newBigFloat64(-7.0, 0, 4), nil}, + {"-seven (10)", newBigFloat64(-7.0, 0, 10), nil}, {"tiny", newBigFloat64(12345.0, -100, 20), nil}, {"mixed", newBigFloat64(12345.0, -10, 20), nil}, @@ -148,8 +156,13 @@ func TestBigFloat(t *testing.T) { {"-Inf", &negInf, nil}, {"+Inf", &posInf, nil}, - {"-0", &negZero, nil}, - {"+0", &posZero, nil}, + + {"-0.0 (0)", newBigFloatNegZero(0), nil}, + {"-0.0 (10)", newBigFloatNegZero(10), nil}, + {"-0.0 (20)", newBigFloatNegZero(20), nil}, + {"+0.0 (0)", newBigFloatPosZero(0), nil}, + {"+0.0 (10)", newBigFloatPosZero(10), nil}, + {"+0.0 (20)", newBigFloatPosZero(20), nil}, {"long whole", wholeNumber, nil}, {"long mixed", mixedNumber, nil}, @@ -159,14 +172,9 @@ func TestBigFloat(t *testing.T) { func TestBigFloatOrdering(t *testing.T) { t.Parallel() - var negInf, posInf, negZero, posZero big.Float + var negInf, posInf big.Float negInf.SetInf(true) posInf.SetInf(false) - negZero.Neg(&negZero) - assert.True(t, negZero.Signbit()) - assert.False(t, posZero.Signbit()) - assert.Equal(t, 0, negZero.Cmp(&posZero)) - assert.NotEqual(t, &negZero, &posZero) // For the same matissa, a higher exponent (first) or precision is closer to infinity. testOrdering(t, lexy.BigFloat(), []testCase[*big.Float]{ @@ -201,8 +209,12 @@ func TestBigFloatOrdering(t *testing.T) { {"-12345.0 * 2^-10000 (19)", newBigFloat64(-12345.0, -10000, 19), nil}, // zeros - {"-0.0", &negZero, nil}, - {"+0.0", &posZero, nil}, + {"-0.0 (20)", newBigFloatNegZero(20), nil}, + {"-0.0 (10)", newBigFloatNegZero(10), nil}, + {"-0.0 (0)", newBigFloatNegZero(0), nil}, + {"+0.0 (0)", newBigFloatPosZero(0), nil}, + {"+0.0 (10)", newBigFloatPosZero(10), nil}, + {"+0.0 (20)", newBigFloatPosZero(20), nil}, // very small positive numbers {"12345.0 * 2^-10000 (19)", newBigFloat64(12345.0, -10000, 19), nil}, diff --git a/float_test.go b/float_test.go index 9072e13..e9e803d 100644 --- a/float_test.go +++ b/float_test.go @@ -12,10 +12,8 @@ import ( // Bit masks for the sign, exponent, and matissa of the // IEEE 754 32- and 64- floating point representations. const ( - maskSign32 uint32 = 0x80_00_00_00 maskExp32 uint32 = 0x7F_80_00_00 maskMant32 uint32 = 0x00_7F_FF_FF - maskSign64 uint64 = 0x80_00_00_00_00_00_00_00 maskExp64 uint64 = 0x7F_F0_00_00_00_00_00_00 maskMant64 uint64 = 0x00_0F_FF_FF_FF_FF_FF_FF ) diff --git a/fuzz-all.sh b/fuzz-all.sh index 67499bd..495fefd 100755 --- a/fuzz-all.sh +++ b/fuzz-all.sh @@ -16,6 +16,6 @@ do do echo "Fuzzing $func in $file" parentDir=$(dirname $file) - go test $parentDir -run=$func -fuzz=$func -fuzztime=${fuzzTime}s + go test $parentDir -run=$func -fuzz=$func -fuzztime=${fuzzTime}s -timeout 0 done done