diff --git a/src/math/big/float.go b/src/math/big/float.go index a8a1eead6ae..3464192aee2 100644 --- a/src/math/big/float.go +++ b/src/math/big/float.go @@ -660,23 +660,66 @@ func high64(x nat) uint64 { return v } -// TODO(gri) FIX THIS (Inf, rounding mode, errors, accuracy, etc.) -func (x *Float) Uint64() uint64 { - m := high64(x.mant) - s := x.exp - if s >= 0 { - return m >> (64 - uint(s)) +// Uint64 returns the unsigned integer resulting from truncating x +// towards zero. If 0 <= x < 2**64, the result is Exact if x is an +// integer; and Below if x has a fractional part. The result is (0, +// Above) for x < 0, and (math.MaxUint64, Below) for x > math.MaxUint64. +func (x *Float) Uint64() (uint64, Accuracy) { + // TODO(gri) there ought to be an easier way to implement this efficiently + if debugFloat { + x.validate() } - return 0 // imprecise + // pick off easy cases + if x.exp <= 0 { + // |x| < 1 || |x| == Inf + if x.exp == infExp { + // ±Inf + if x.neg { + return 0, Above // -Inf + } + return math.MaxUint64, Below // +Inf + } + if len(x.mant) == 0 { + return 0, Exact // ±0 + } + // 0 < |x| < 1 + if x.neg { + return 0, Above + } + return 0, Below + } + // x.exp > 0 + if x.neg { + return 0, Above + } + // x > 0 + if x.exp <= 64 { + // u = trunc(x) fits into a uint64 + u := high64(x.mant) >> (64 - uint32(x.exp)) + // x.mant[len(x.mant)-1] != 0 + // determine minimum required precision for x + minPrec := uint(len(x.mant))*_W - x.mant.trailingZeroBits() + if minPrec <= 64 { + return u, Exact + } + return u, Below + } + // x is too large + return math.MaxUint64, Below } // TODO(gri) FIX THIS (inf, rounding mode, errors, etc.) func (x *Float) Int64() int64 { - v := int64(x.Uint64()) - if x.neg { - return -v + m := high64(x.mant) + s := x.exp + var i int64 + if s >= 0 { + i = int64(m >> (64 - uint(s))) } - return v + if x.neg { + return -i + } + return i } // Float64 returns the closest float64 value of x diff --git a/src/math/big/float_test.go b/src/math/big/float_test.go index 58fab4605aa..3ec8e831315 100644 --- a/src/math/big/float_test.go +++ b/src/math/big/float_test.go @@ -13,6 +13,14 @@ import ( "testing" ) +func (x *Float) uint64() uint64 { + u, acc := x.Uint64() + if acc != Exact { + panic(fmt.Sprintf("%s is not a uint64", x.Format('g', 10))) + } + return u +} + func TestFloatZeroValue(t *testing.T) { // zero (uninitialized) value is a ready-to-use 0.0 var x Float @@ -52,14 +60,18 @@ func TestFloatZeroValue(t *testing.T) { {1, 2, 0, 0, '*', (*Float).Mul}, {2, 0, 1, 0, '*', (*Float).Mul}, - {0, 0, 0, 0, '/', (*Float).Quo}, + {0, 0, 0, 0, '/', (*Float).Quo}, // = +Inf {0, 2, 1, 2, '/', (*Float).Quo}, - {1, 2, 0, 0, '/', (*Float).Quo}, + {1, 2, 0, 0, '/', (*Float).Quo}, // = +Inf {2, 0, 1, 0, '/', (*Float).Quo}, } { z := make(test.z) test.op(z, make(test.x), make(test.y)) - if got := int(z.Int64()); got != test.want { + got := 0 + if !z.IsInf(0) { + got = int(z.Int64()) + } + if got != test.want { t.Errorf("%d %c %d = %d; want %d", test.x, test.opname, test.y, got, test.want) } } @@ -384,7 +396,7 @@ func TestFloatSetUint64(t *testing.T) { } { var f Float f.SetUint64(want) - if got := f.Uint64(); got != want { + if got := f.uint64(); got != want { t.Errorf("got %#x (%s); want %#x", got, f.Format('p', 0), want) } } @@ -393,7 +405,7 @@ func TestFloatSetUint64(t *testing.T) { const x uint64 = 0x8765432187654321 // 64 bits needed for prec := uint(1); prec <= 64; prec++ { f := NewFloat(0, prec, ToZero).SetUint64(x) - got := f.Uint64() + got := f.uint64() want := x &^ (1<<(64-prec) - 1) // cut off (round to zero) low 64-prec bits if got != want { t.Errorf("got %#x (%s); want %#x", got, f.Format('p', 0), want) @@ -553,6 +565,32 @@ func TestFloatSetRat(t *testing.T) { } } +func TestFloatUint64(t *testing.T) { + for _, test := range []struct { + x string + out uint64 + acc Accuracy + }{ + {"0", 0, Exact}, + {"-0", 0, Exact}, + {"-1", 0, Above}, + {"-Inf", 0, Above}, + {"-1e-1000", 0, Above}, + {"1e-1000", 0, Below}, + {"12345.0", 12345, Exact}, + {"12345.6", 12345, Below}, + {"18446744073709551615", 18446744073709551615, Exact}, + {"18446744073709551615.000000000000000000001", math.MaxUint64, Below}, + {"1e10000", math.MaxUint64, Below}, + } { + x := makeFloat(test.x) + out, acc := x.Uint64() + if out != test.out || acc != test.acc { + t.Errorf("%s: got %d (%s); want %d (%s)", test.x, out, acc, test.out, test.acc) + } + } +} + func TestFloatInt(t *testing.T) { for _, test := range []struct { x string