Skip to content

Commit bde549e

Browse files
authored
Add Coerce functions to Number (#404)
* Add `Coerse` functions to `Number` Expands/replaces #143
1 parent ddb4e2e commit bde549e

File tree

2 files changed

+172
-1
lines changed

2 files changed

+172
-1
lines changed

msgp/number.go

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package msgp
22

33
import (
44
"math"
5+
"math/bits"
56
"strconv"
67
)
78

@@ -77,7 +78,7 @@ func (n *Number) Uint() (uint64, bool) {
7778
}
7879

7980
// Float casts the number to a float64, and
80-
// returns whether or not that was the underlying
81+
// returns whether that was the underlying
8182
// type (either a float64 or a float32).
8283
func (n *Number) Float() (float64, bool) {
8384
switch n.typ {
@@ -208,6 +209,111 @@ func (n *Number) EncodeMsg(w *Writer) error {
208209
}
209210
}
210211

212+
// CoerceInt attempts to coerce the value of
213+
// the number into a signed integer and returns
214+
// whether it was successful.
215+
// "Success" implies that no precision in the value of
216+
// the number was lost, which means that the number was an integer or
217+
// a floating point that mapped exactly to an integer without rounding.
218+
func (n *Number) CoerceInt() (int64, bool) {
219+
switch n.typ {
220+
case InvalidType, IntType:
221+
// InvalidType just means un-initialized.
222+
return int64(n.bits), true
223+
case UintType:
224+
return int64(n.bits), n.bits <= math.MaxInt64
225+
case Float32Type:
226+
f := math.Float32frombits(uint32(n.bits))
227+
if n.isExactInt() && f <= math.MaxInt64 && f >= math.MinInt64 {
228+
return int64(f), true
229+
}
230+
if n.bits == 0 || n.bits == 1<<31 {
231+
return 0, true
232+
}
233+
case Float64Type:
234+
f := math.Float64frombits(n.bits)
235+
if n.isExactInt() && f <= math.MaxInt64 && f >= math.MinInt64 {
236+
return int64(f), true
237+
}
238+
return 0, n.bits == 0 || n.bits == 1<<63
239+
}
240+
return 0, false
241+
}
242+
243+
// CoerceUInt attempts to coerce the value of
244+
// the number into an unsigned integer and returns
245+
// whether it was successful.
246+
// "Success" implies that no precision in the value of
247+
// the number was lost, which means that the number was an integer or
248+
// a floating point that mapped exactly to an integer without rounding.
249+
func (n *Number) CoerceUInt() (uint64, bool) {
250+
switch n.typ {
251+
case InvalidType, IntType:
252+
// InvalidType just means un-initialized.
253+
if int64(n.bits) >= 0 {
254+
return n.bits, true
255+
}
256+
case UintType:
257+
return n.bits, true
258+
case Float32Type:
259+
f := math.Float32frombits(uint32(n.bits))
260+
if f >= 0 && f <= math.MaxUint64 && n.isExactInt() {
261+
return uint64(f), true
262+
}
263+
if n.bits == 0 || n.bits == 1<<31 {
264+
return 0, true
265+
}
266+
case Float64Type:
267+
f := math.Float64frombits(n.bits)
268+
if f >= 0 && f <= math.MaxUint64 && n.isExactInt() {
269+
return uint64(f), true
270+
}
271+
return 0, n.bits == 0 || n.bits == 1<<63
272+
}
273+
return 0, false
274+
}
275+
276+
// isExactInt will return true if the number represents an integer value.
277+
// NaN, Inf returns false.
278+
func (n *Number) isExactInt() bool {
279+
var eBits int // Exponent bits
280+
var mBits int // Mantissa bits
281+
282+
switch n.typ {
283+
case InvalidType, IntType, UintType:
284+
return true
285+
case Float32Type:
286+
eBits = 8
287+
mBits = 23
288+
case Float64Type:
289+
eBits = 11
290+
mBits = 52
291+
default:
292+
return false
293+
}
294+
// Calculate float parts
295+
exp := int(n.bits>>mBits) & ((1 << eBits) - 1)
296+
mant := n.bits & ((1 << mBits) - 1)
297+
if exp == 0 && mant == 0 {
298+
// Handle zero value.
299+
return true
300+
}
301+
302+
exp -= (1 << (eBits - 1)) - 1
303+
if exp < 0 || exp == 1<<(eBits-1) {
304+
// Negative exponent is never integer (except zero handled above)
305+
// Handles NaN (exp all 1s)
306+
return false
307+
}
308+
309+
if exp >= mBits {
310+
// If we have more exponent than mantissa bits it is always an integer.
311+
return true
312+
}
313+
// Check if all bits below the exponent are zero.
314+
return bits.TrailingZeros64(mant) >= mBits-exp
315+
}
316+
211317
// Msgsize implements msgp.Sizer
212318
func (n *Number) Msgsize() int {
213319
switch n.typ {

msgp/number_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package msgp
22

33
import (
44
"bytes"
5+
"math"
56
"testing"
67
)
78

@@ -93,3 +94,67 @@ func TestNumber(t *testing.T) {
9394
t.Errorf("encode: expected output %#v; got %#v", dat, buf.Bytes())
9495
}
9596
}
97+
98+
func TestConv32(t *testing.T) {
99+
for i := -1 << 24; i < 1<<25; i++ {
100+
x := math.Float32bits(float32(i))
101+
exp := int(x>>23)&255 - 127
102+
mant := x & ((1 << 23) - 1)
103+
isExact := false
104+
if exp >= 23 || (exp == -127 && mant == 0) {
105+
isExact = true
106+
}
107+
// Only exp >= 0 can be exact integer
108+
if exp >= 0 && exp <= 23 {
109+
mantissaMask := uint32((1 << (23 - exp)) - 1)
110+
isExact = (mant & mantissaMask) == 0
111+
}
112+
113+
n := Number{bits: uint64(x), typ: Float32Type}
114+
got := n.isExactInt()
115+
if got != isExact {
116+
t.Errorf("n.IsExactInt(): got %t, want %t", got, isExact)
117+
}
118+
n = Number{bits: uint64(math.Float32bits(float32(i) + 0.1)), typ: Float32Type}
119+
got = n.isExactInt()
120+
if got != false && i > -2097152 && i < 2097152 {
121+
val, ok := n.Float()
122+
t.Fatalf("n.IsExactInt(%f): got %t, want %t, ok: %v", val, got, false, ok)
123+
}
124+
}
125+
}
126+
127+
func TestConv64(t *testing.T) {
128+
for i := -1 << 30; i < 1<<30; i++ {
129+
if testing.Short() {
130+
i += 8
131+
}
132+
x := math.Float64bits(float64(i))
133+
exp := int(x>>52)&2047 - 1023
134+
mant := x & ((1 << 52) - 1)
135+
136+
isExact := false
137+
if exp >= 52 || (exp == -1023 && mant == 0) {
138+
isExact = true
139+
}
140+
// Only exp >= 0 can be exact integer
141+
if exp >= 0 && exp <= 52 {
142+
mantissaMask := uint64((1 << (52 - exp)) - 1)
143+
isExact = (mant & mantissaMask) == 0
144+
}
145+
n := Number{bits: uint64(x), typ: Float64Type}
146+
got := n.isExactInt()
147+
if got != isExact {
148+
t.Errorf("n.IsExactInt(): got %t, want %t", got, isExact)
149+
}
150+
if !got {
151+
t.Fatal(i)
152+
}
153+
n = Number{bits: uint64(math.Float64bits(float64(i) + 0.1)), typ: Float64Type}
154+
got = n.isExactInt()
155+
if got != false {
156+
val, ok := n.Float()
157+
t.Fatalf("n.IsExactInt(%f): got %t, want %t, ok: %v", val, got, false, ok)
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)