Skip to content

Commit b2d43eb

Browse files
authored
Fix formatting and add Round/ToIntegral (#75)
1 parent 751da49 commit b2d43eb

16 files changed

+1161
-70
lines changed

decimal64.go

+3-12
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ const (
3131
roundDown
3232
)
3333

34-
// Decimal64 represents an IEEE 754 64-bit floating point decimal number.
35-
// It uses the binary representation method.
36-
// Decimal64 is intentionally a struct to ensure users don't accidentally cast it to uint64
37-
type Decimal64 struct {
38-
bits uint64
39-
debugInfo //nolint:unused
40-
}
41-
4234
// Context64 stores the rounding type for arithmetic operations.
4335
type Context64 struct {
4436
roundingMode roundingMode
@@ -113,7 +105,6 @@ func New64FromInt64(value int64) Decimal64 {
113105
}
114106

115107
func renormalize(exp int, significand uint64) (int, uint64) {
116-
117108
numBits := 64 - bits.LeadingZeros64(significand)
118109
numDigits := numBits * 3 / 10
119110
normExp := 15 - numDigits
@@ -192,12 +183,12 @@ func newFromParts(sign int, exp int, significand uint64) Decimal64 {
192183
if significand < 0x8<<50 {
193184
// s EEeeeeeeee (0)ttt tttttttttt tttttttttt tttttttttt tttttttttt tttttttttt
194185
// EE ∈ {00, 01, 10}
195-
return Decimal64{bits: s | uint64(exp+expOffset)<<(63-10) | significand}.debug()
186+
return new64(s | uint64(exp+expOffset)<<(63-10) | significand)
196187
}
197188
// s 11EEeeeeeeee (100)t tttttttttt tttttttttt tttttttttt tttttttttt tttttttttt
198189
// EE ∈ {00, 01, 10}
199190
significand &= 0x8<<50 - 1
200-
return Decimal64{bits: s | uint64(0xc00|(exp+expOffset))<<(63-12) | significand}.debug()
191+
return new64(s | uint64(0xc00|(exp+expOffset))<<(63-12) | significand)
201192
}
202193

203194
func (d Decimal64) parts() (fl flavor, sign int, exp int, significand uint64) {
@@ -357,7 +348,7 @@ func (d Decimal64) IsInt() bool {
357348

358349
// quiet returns a quiet form of d, which must be a NaN.
359350
func (d Decimal64) quiet() Decimal64 {
360-
return Decimal64{bits: d.bits &^ (2 << 56)}.debug()
351+
return new64(d.bits &^ (2 << 56))
361352
}
362353

363354
// IsSubnormal returns true iff d is a subnormal.

decimal64_debug.go

+19-17
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,31 @@ package decimal
55

66
// Decimal64 represents an IEEE 754 64-bit floating point decimal number.
77
// It uses the binary representation method.
8-
// Decimal64 is intentionally a struct to ensure users don't accidentally cast it to uint64
9-
type debugInfo struct {
8+
// Decimal64 is intentionally a struct to ensure users don't accidentally cast it to uint64.
9+
type Decimal64 struct {
1010
s string
1111
fl flavor
1212
sign int
1313
exp int
1414
significand uint64
15+
bits uint64
1516
}
1617

17-
func (d Decimal64) debug() Decimal64 {
18-
s := d.s
19-
if s == "" {
20-
s = d.String()
21-
}
18+
// This should be the only point at which Decimal64 instances are constructed raw.
19+
// The verbose construction below makes it easy to audit accidental raw cosntruction.
20+
// A search for (?<!\[\])Decimal64\{ must come up empty.
21+
func new64(bits uint64) Decimal64 {
22+
var d Decimal64
23+
d.bits = bits
24+
2225
fl, sign, exp, significand := d.parts()
23-
return Decimal64{
24-
bits: d.bits,
25-
debugInfo: debugInfo{
26-
s: s,
27-
fl: fl,
28-
sign: sign,
29-
exp: exp,
30-
significand: significand,
31-
},
32-
}
26+
27+
d.s = d.String()
28+
d.fl = fl
29+
d.sign = sign
30+
d.exp = exp
31+
d.significand = significand
32+
d.bits = d.bits
33+
34+
return d
3335
}

decimal64_ndebug.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,16 @@ package decimal
55

66
// Decimal64 represents an IEEE 754 64-bit floating point decimal number.
77
// It uses the binary representation method.
8-
// Decimal64 is intentionally a struct to ensure users don't accidentally cast it to uint64
9-
type debugInfo struct{} //nolint:unused
8+
// Decimal64 is intentionally a struct to ensure users don't accidentally cast it to uint64.
9+
type Decimal64 struct {
10+
bits uint64
11+
}
1012

11-
func (d Decimal64) debug() Decimal64 {
13+
// This should be the only point at which Decimal64 instances are constructed raw.
14+
// The verbose construction below makes it easy to audit accidental raw cosntruction.
15+
// A search for (?<!\[\])Decimal64\{ must come up empty.
16+
func new64(bits uint64) Decimal64 {
17+
var d Decimal64
18+
d.bits = bits
1219
return d
1320
}

decimal64_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func TestDecimal64isZero(t *testing.T) {
151151
require := require.New(t)
152152

153153
require.Equal(true, Zero64.IsZero())
154-
require.Equal(true, Decimal64{bits: Zero64.bits | neg64}.IsZero())
154+
require.Equal(true, NegZero64.IsZero())
155155
require.Equal(false, One64.IsZero())
156156
}
157157

decimal64const.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,25 @@ var One64 = newFromParts(0, -15, decimal64Base)
1313
var NegOne64 = newFromParts(1, -15, decimal64Base)
1414

1515
// Infinity64 represents ∞ as a Decimal64.
16-
var Infinity64 = Decimal64{bits: inf64}.debug()
16+
var Infinity64 = new64(inf64)
1717

1818
// NegInfinity64 represents -∞ as a Decimal64.
19-
var NegInfinity64 = Decimal64{bits: neg64 | inf64}.debug()
19+
var NegInfinity64 = new64(neg64 | inf64)
2020

2121
// QNaN64 represents a quiet NaN as a Decimal64.
22-
var QNaN64 = Decimal64{bits: 0x7c << 56}.debug()
22+
var QNaN64 = new64(0x7c << 56)
2323

2424
// SNaN64 represents a signalling NaN as a Decimal64.
25-
var SNaN64 = Decimal64{bits: 0x7e << 56}.debug()
25+
var SNaN64 = new64(0x7e << 56)
2626

2727
// Pi64 represents π.
2828
var Pi64 = newFromParts(0, -15, 3_141592653589793)
2929

3030
// E64 represents e (lim[n→∞](1+1/n)ⁿ).
3131
var E64 = newFromParts(0, -15, 2_718281828459045)
3232

33-
var neg64 uint64 = 0x80 << 56
34-
var inf64 uint64 = 0x78 << 56
33+
const neg64 uint64 = 0x80 << 56
34+
const inf64 uint64 = 0x78 << 56
3535

3636
// 1E15
3737
const decimal64Base uint64 = 1_000_000_000_000_000

decimal64decParts.go

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ type decParts struct {
99
original Decimal64
1010
}
1111

12+
func unpack(d Decimal64) decParts {
13+
var dp decParts
14+
dp.unpack(d)
15+
return dp
16+
}
17+
1218
// add128 adds two decParts with full precision in 128 bits of significand
1319
func (dp *decParts) add128(ep *decParts) decParts {
1420
dp.matchScales128(ep)

decimal64fmt.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
"strconv"
77
)
88

9-
var _ fmt.Formatter = Decimal64{}
9+
var _ fmt.Formatter = Zero64
1010
var _ fmt.Scanner = (*Decimal64)(nil)
11-
var _ fmt.Stringer = Decimal64{}
11+
var _ fmt.Stringer = Zero64
1212

1313
func appendFrac64(buf []byte, n, limit uint64) []byte {
1414
for n > 0 {
@@ -102,13 +102,20 @@ func (d Decimal64) append(buf []byte, format byte, width, prec int) []byte {
102102
formatBlock:
103103
switch format {
104104
case 'e', 'E':
105+
// normalise subnormals
106+
exp, significand = unsubnormal(exp, significand)
107+
105108
whole := significand / decimal64Base
106109
buf = append(buf, byte('0'+whole))
107110
frac := significand - decimal64Base*whole
108111
if frac > 0 {
109112
buf = appendFrac64(append(buf, '.'), frac, decimal64Base/10)
110113
}
111114

115+
if significand == 0 {
116+
return buf
117+
}
118+
112119
exp += 15
113120
if exp != 0 {
114121
buf = append(buf, format)
@@ -149,7 +156,9 @@ formatBlock:
149156
}
150157
return buf
151158
case 'g', 'G':
152-
if exp < -decimal64Digits-4 || prec >= 0 && exp > prec {
159+
if exp < -decimal64Digits-3 ||
160+
prec >= 0 && exp > prec ||
161+
prec < 0 && exp > -decimal64Digits+6 {
153162
format -= 'g' - 'e'
154163
} else {
155164
format -= 'g' - 'f'

decimal64fmt_test.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@ func TestDecimal64String(t *testing.T) {
3838
}
3939
}
4040

41+
func TestDecimal64StringEdgeCases(t *testing.T) {
42+
t.Parallel()
43+
44+
require.Equal(t, "123456", MustParse64("123456").String())
45+
require.Equal(t, "-123456", MustParse64("-123456").String())
46+
require.Equal(t, "1.234567e+6", MustParse64("1234567").String())
47+
require.Equal(t, "-1.234567e+6", MustParse64("-1234567").String())
48+
require.Equal(t, "0.0001", MustParse64("0.0001").String())
49+
require.Equal(t, "-0.0001", MustParse64("-0.0001").String())
50+
require.Equal(t, "1e-5", MustParse64("0.00001").String())
51+
require.Equal(t, "-1e-5", MustParse64("-0.00001").String())
52+
require.Equal(t, "9.999999999999999e+384", MustParse64("9.999999999999999e+384").String())
53+
require.Equal(t, "-9.999999999999999e+384", MustParse64("-9.999999999999999e+384").String())
54+
require.Equal(t, "1e-398", MustParse64("1e-398").String())
55+
require.Equal(t, "-1e-398", MustParse64("-1e-398").String())
56+
}
57+
4158
func BenchmarkDecimal64String(b *testing.B) {
4259
d := New64FromInt64(123456789)
4360
for i := 0; i <= b.N; i++ {
@@ -116,7 +133,7 @@ func TestDecimal64FormatPrec(t *testing.T) {
116133

117134
// Add five digits to the significand so we round at a 2.
118135
pi = pi.Add(New64FromInt64(10_100_000_000))
119-
assert.Equal(t, "10100100103.14159", fmt.Sprintf("%v", pi))
136+
assert.Equal(t, "1.010010010314159e+10", fmt.Sprintf("%v", pi))
120137
assert.Equal(t, "10100100103.141590", fmt.Sprintf("%f", pi))
121138
assert.Equal(t, "10100100103", fmt.Sprintf("%.0f", pi))
122139
assert.Equal(t, "10100100103.1", fmt.Sprintf("%.1f", pi))

decimal64gob.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
)
66

77
var _ gob.GobDecoder = (*Decimal64)(nil)
8-
var _ gob.GobEncoder = Decimal64{}
8+
var _ gob.GobEncoder = Zero64
99

1010
// GobDecode implements encoding.GobDecoder.
1111
func (d *Decimal64) GobDecode(buf []byte) error {

decimal64json.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package decimal
22

33
import "encoding/json"
44

5-
var _ json.Marshaler = Decimal64{}
5+
var _ json.Marshaler = Zero64
66
var _ json.Unmarshaler = (*Decimal64)(nil)
77

88
// MarshalText implements the encoding.TextMarshaler interface.

decimal64marshal.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"fmt"
77
)
88

9-
var _ encoding.TextMarshaler = Decimal64{}
9+
var _ encoding.TextMarshaler = Zero64
1010
var _ encoding.TextUnmarshaler = (*Decimal64)(nil)
1111

1212
// MarshalText implements the encoding.TextMarshaler interface.
@@ -26,7 +26,7 @@ func (d *Decimal64) UnmarshalText(text []byte) error {
2626
return err
2727
}
2828

29-
var _ encoding.BinaryMarshaler = Decimal64{}
29+
var _ encoding.BinaryMarshaler = Zero64
3030
var _ encoding.BinaryUnmarshaler = (*Decimal64)(nil)
3131

3232
// MarshalBinary implements the encoding.BinaryMarshaler interface.
@@ -38,7 +38,8 @@ func (d Decimal64) MarshalBinary() ([]byte, error) {
3838

3939
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
4040
func (d *Decimal64) UnmarshalBinary(data []byte) error {
41-
d.bits = binary.BigEndian.Uint64(data)
41+
bits := binary.BigEndian.Uint64(data)
4242
// TODO: Check for out of bounds significand.
43+
*d = new64(bits)
4344
return nil
4445
}

0 commit comments

Comments
 (0)