From 2a1728d0094f0ffc66231ec415522bf54ca37293 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 3 Mar 2015 14:17:39 -0800 Subject: [PATCH] math/big: use stringer for enum String() methods Change-Id: Ide0615542d67b7d81bf6c56aab550e142a8789f7 Reviewed-on: https://go-review.googlesource.com/6682 Reviewed-by: Alan Donovan --- src/math/big/accuracy_string.go | 16 +++ src/math/big/float.go | 181 ++++++++++++---------------- src/math/big/float_test.go | 46 +++++-- src/math/big/floatconv.go | 8 +- src/math/big/floatexample_test.go | 8 +- src/math/big/roundingmode_string.go | 16 +++ 6 files changed, 151 insertions(+), 124 deletions(-) create mode 100644 src/math/big/accuracy_string.go create mode 100644 src/math/big/roundingmode_string.go diff --git a/src/math/big/accuracy_string.go b/src/math/big/accuracy_string.go new file mode 100644 index 0000000000..647a1fb6d3 --- /dev/null +++ b/src/math/big/accuracy_string.go @@ -0,0 +1,16 @@ +// generated by stringer -type=Accuracy; DO NOT EDIT + +package big + +import "fmt" + +const _Accuracy_name = "ExactBelowAboveUndef" + +var _Accuracy_index = [...]uint8{0, 5, 10, 15, 20} + +func (i Accuracy) String() string { + if i < 0 || i+1 >= Accuracy(len(_Accuracy_index)) { + return fmt.Sprintf("Accuracy(%d)", i) + } + return _Accuracy_name[_Accuracy_index[i]:_Accuracy_index[i+1]] +} diff --git a/src/math/big/float.go b/src/math/big/float.go index de16c6eccd..0ad8312afe 100644 --- a/src/math/big/float.go +++ b/src/math/big/float.go @@ -18,15 +18,15 @@ import ( const debugFloat = true // enable for debugging -// A nonzero Float represents a multi-precision floating point number +// A nonzero finite Float represents a multi-precision floating point number // // sign × mantissa × 2**exponent // // with 0.5 <= mantissa < 1.0, and MinExp <= exponent <= MaxExp. -// A Float may also be +0, -0, +Inf, -Inf, or NaN. +// A Float may also be zero (+0, -0), infinite (+Inf, -Inf) or +// not-a-number (NaN). // // Each Float value also has a precision, rounding mode, and accuracy. -// // The precision is the maximum number of mantissa bits available to // represent the value. The rounding mode specifies how a result should // be rounded to fit into the mantissa bits, and accuracy describes the @@ -46,21 +46,20 @@ const debugFloat = true // enable for debugging // // By setting the desired precision to 24 or 53 and using matching rounding // mode (typically ToNearestEven), Float operations produce the same results -// as the corresponding float32 or float64 IEEE-754 arithmetic for normalized -// operands (including +0 and -0). Exponent underflow and overflow lead to a -// 0 or an Infinity for different values than IEEE-754 because Float exponents -// hace a much larger range. +// as the corresponding float32 or float64 IEEE-754 arithmetic. Exponent +// underflow and overflow lead to a 0 or an Infinity for different values +// than IEEE-754 because Float exponents have a much larger range. // // The zero (uninitialized) value for a Float is ready to use and represents // the number +0.0 exactly, with precision 0 and rounding mode ToNearestEven. // type Float struct { + prec uint32 mode RoundingMode acc Accuracy neg bool mant nat exp int32 - prec uint32 } // Internal representation: The mantissa bits x.mant of a Float x are stored @@ -81,38 +80,10 @@ const ( MaxPrec = math.MaxUint32 // largest (theoretically) supported precision; likely memory-limited ) -// Accuracy describes the rounding error produced by the most recent -// operation that generated a Float value, relative to the exact value. -// The accuracy may be Undef for operations on and resulting in -// NaNs since they are neither Below nor Above any other value. -type Accuracy int8 - -// Constants describing the Accuracy of a Float. -const ( - Exact Accuracy = 0 - Below Accuracy = 1 << 0 - Above Accuracy = 1 << 1 - Undef Accuracy = Below | Above -) - -func (a Accuracy) String() string { - switch a { - case Exact: - return "exact" - case Below: - return "below" - case Above: - return "above" - case Undef: - return "undef" - } - panic(fmt.Sprintf("unknown accuracy %d", a)) -} - // RoundingMode determines how a Float value is rounded to the // desired precision. Rounding may change the Float value; the // rounding error is described by the Float's Accuracy. -type RoundingMode uint8 +type RoundingMode byte // The following rounding modes are supported. const ( @@ -124,23 +95,23 @@ const ( ToPositiveInf // == IEEE 754-2008 roundTowardPositive ) -func (mode RoundingMode) String() string { - switch mode { - case ToNearestEven: - return "ToNearestEven" - case ToNearestAway: - return "ToNearestAway" - case ToZero: - return "ToZero" - case AwayFromZero: - return "AwayFromZero" - case ToNegativeInf: - return "ToNegativeInf" - case ToPositiveInf: - return "ToPositiveInf" - } - panic("unreachable") -} +//go:generate stringer -type=RoundingMode + +// Accuracy describes the rounding error produced by the most recent +// operation that generated a Float value, relative to the exact value. +// The accuracy is Undef for operations on and resulting in NaNs since +// they are neither Below nor Above any other value. +type Accuracy byte + +// Constants describing the Accuracy of a Float. +const ( + Exact Accuracy = 0 + Below Accuracy = 1 << 0 + Above Accuracy = 1 << 1 + Undef Accuracy = Below | Above +) + +//go:generate stringer -type=Accuracy // SetPrec sets z's precision to prec and returns the (possibly) rounded // value of z. Rounding occurs according to z's rounding mode if the mantissa @@ -179,9 +150,10 @@ func (z *Float) SetPrec(prec uint) *Float { // SetMode sets z's rounding mode to mode and returns an exact z. // z remains unchanged otherwise. +// z.SetMode(z.Mode()) is a cheap way to set z's accuracy to Exact. func (z *Float) SetMode(mode RoundingMode) *Float { - z.acc = Exact // TODO(gri) should we not do this? what's the general rule for setting accuracy? z.mode = mode + z.acc = Exact return z } @@ -198,16 +170,16 @@ func (x *Float) MinPrec() uint { return uint(len(x.mant))*_W - x.mant.trailingZeroBits() } -// Acc returns the accuracy of x produced by the most recent operation. -func (x *Float) Acc() Accuracy { - return x.acc -} - // Mode returns the rounding mode of x. func (x *Float) Mode() RoundingMode { return x.mode } +// Acc returns the accuracy of x produced by the most recent operation. +func (x *Float) Acc() Accuracy { + return x.acc +} + // Sign returns: // // -1 if x < 0 @@ -288,6 +260,33 @@ func (z *Float) SetMantExp(mant *Float, exp int) *Float { return z } +// IsNeg reports whether x is negative. +// A NaN is not negative. +func (x *Float) IsNeg() bool { + return x.neg && x.exp != nanExp +} + +// IsZero reports whether x is a +0 or -0. +func (x *Float) IsZero() bool { + return len(x.mant) == 0 && x.exp == 0 +} + +// IsFinite reports whether -Inf < x < Inf. +// A NaN is not finite. +func (x *Float) IsFinite() bool { + return len(x.mant) != 0 || x.exp == 0 +} + +// IsInf reports whether x is a +Inf or -Inf. +func (x *Float) IsInf() bool { + return x.exp == infExp +} + +// IsNaN reports whether x is a NaN. +func (x *Float) IsNaN() bool { + return x.exp == nanExp +} + // IsInt reports whether x is an integer. // ±Inf and NaN are not considered integers. func (x *Float) IsInt() bool { @@ -303,25 +302,6 @@ func (x *Float) IsInt() bool { return x.prec <= uint32(x.exp) || x.MinPrec() <= uint(x.exp) // not enough bits for fractional mantissa } -// IsInf reports whether x is an infinity, according to sign. -// If sign > 0, IsInf reports whether x is positive infinity. -// If sign < 0, IsInf reports whether x is negative infinity. -// If sign == 0, IsInf reports whether x is either infinity. -func (x *Float) IsInf(sign int) bool { - if debugFloat { - validate(x) - } - return x.exp == infExp && (sign == 0 || x.neg == (sign < 0)) -} - -// IsNaN reports whether x is a NaN. -func (x *Float) IsNaN() bool { - if debugFloat { - validate(x) - } - return x.exp == nanExp -} - func (z *Float) setZero() { z.mant = z.mant[:0] z.exp = 0 @@ -715,20 +695,20 @@ func (z *Float) Set(x *Float) *Float { return z } -// Copy sets z to x, with the same precision and rounding mode as x, -// and returns z. +// Copy sets z to x, with the same precision, rounding mode, and +// accuracy as x, and returns z. x is not changed even if z and +// x are the same. func (z *Float) Copy(x *Float) *Float { if debugFloat { validate(x) } - // TODO(gri) what about z.acc? should it be always Exact? if z != x { - z.acc = Exact - z.neg = x.neg - z.exp = x.exp - z.mant = z.mant.set(x.mant) z.prec = x.prec z.mode = x.mode + z.acc = x.acc + z.neg = x.neg + z.mant = z.mant.set(x.mant) + z.exp = x.exp } return z } @@ -858,7 +838,7 @@ func (x *Float) Int64() (int64, Accuracy) { // Float64 returns the closest float64 value of x // by rounding to nearest with 53 bits precision. -// BUG(gri) doesn't handle exponent overflow +// BUG(gri) Float.Float64 doesn't handle exponent overflow. func (x *Float) Float64() (float64, Accuracy) { if debugFloat { validate(x) @@ -955,7 +935,6 @@ func (x *Float) Int(z *Int) (*Int, Accuracy) { z = new(Int) } z.neg = x.neg - // TODO(gri) should have a shift that takes positive and negative shift counts switch { case exp > allBits: z.abs = z.abs.shl(x.mant, exp-allBits) @@ -1232,8 +1211,7 @@ func (x *Float) ucmp(y *Float) int { return 0 } -// Handling of sign bit as defined by IEEE 754-2008, -// section 6.3 (note that there are no NaN Floats): +// Handling of sign bit as defined by IEEE 754-2008, section 6.3: // // When neither the inputs nor result are NaN, the sign of a product or // quotient is the exclusive OR of the operands’ signs; the sign of a sum, @@ -1252,14 +1230,13 @@ func (x *Float) ucmp(y *Float) int { // // See also: http://play.golang.org/p/RtH3UCt5IH -// Add sets z to the rounded sum x+y and returns z. -// If z's precision is 0, it is changed to the larger -// of x's or y's precision before the operation. -// Rounding is performed according to z's precision -// and rounding mode; and z's accuracy reports the -// result error relative to the exact (not rounded) +// Add sets z to the rounded sum x+y and returns z. If z's precision is 0, +// it is changed to the larger of x's or y's precision before the operation. +// Rounding is performed according to z's precision and rounding mode; and +// z's accuracy reports the result error relative to the exact (not rounded) // result. -// BUG(gri) If any of the operands is Inf, the result is NaN. +// BUG(gri) Float.Add returns NaN if an operand is Inf. +// BUG(gri) When rounding ToNegativeInf, the sign of Float values rounded to 0 is incorrect. func (z *Float) Add(x, y *Float) *Float { if debugFloat { validate(x) @@ -1314,7 +1291,7 @@ func (z *Float) Add(x, y *Float) *Float { // Sub sets z to the rounded difference x-y and returns z. // Precision, rounding, and accuracy reporting are as for Add. -// BUG(gri) If any of the operands is Inf, the result is NaN. +// BUG(gri) Float.Sub returns NaN if an operand is Inf. func (z *Float) Sub(x, y *Float) *Float { if debugFloat { validate(x) @@ -1369,7 +1346,7 @@ func (z *Float) Sub(x, y *Float) *Float { // Mul sets z to the rounded product x*y and returns z. // Precision, rounding, and accuracy reporting are as for Add. -// BUG(gri) If any of the operands is Inf, the result is NaN. +// BUG(gri) Float.Mul returns NaN if an operand is Inf. func (z *Float) Mul(x, y *Float) *Float { if debugFloat { validate(x) @@ -1407,7 +1384,7 @@ func (z *Float) Mul(x, y *Float) *Float { // Quo sets z to the rounded quotient x/y and returns z. // Precision, rounding, and accuracy reporting are as for Add. -// BUG(gri) If any of the operands is Inf, the result is NaN. +// BUG(gri) Float.Quo returns NaN if an operand is Inf. func (z *Float) Quo(x, y *Float) *Float { if debugFloat { validate(x) @@ -1454,8 +1431,7 @@ func (z *Float) Quo(x, y *Float) *Float { // // Infinities with matching sign are equal. // NaN values are never equal. -// BUG(gri) comparing NaN's is not implemented -// (should we use Accuracy here for results?) +// BUG(gri) Float.Cmp does not implement comparing of NaNs. func (x *Float) Cmp(y *Float) int { if debugFloat { validate(x) @@ -1498,7 +1474,6 @@ func umax32(x, y uint32) uint32 { // +1 if 0 < x < +Inf // +2 if x == +Inf // -// TODO(gri) export (and remove IsInf)? func (x *Float) ord() int { m := 1 // common case if len(x.mant) == 0 { diff --git a/src/math/big/float_test.go b/src/math/big/float_test.go index aa1cb5828b..0be6a957d3 100644 --- a/src/math/big/float_test.go +++ b/src/math/big/float_test.go @@ -78,7 +78,7 @@ func TestFloatZeroValue(t *testing.T) { z := make(test.z) test.op(z, make(test.x), make(test.y)) got := 0 - if !z.IsInf(0) && !z.IsNaN() { + if z.IsFinite() { got = int(z.int64()) } if got != test.want { @@ -288,6 +288,38 @@ func TestFloatSetMantExp(t *testing.T) { } } +func TestFloatPredicates(t *testing.T) { + for _, test := range []struct { + x string + neg, zero, finite, inf, nan bool + }{ + {x: "-Inf", neg: true, inf: true}, + {x: "-1", neg: true, finite: true}, + {x: "-0", neg: true, zero: true, finite: true}, + {x: "0", zero: true, finite: true}, + {x: "1", finite: true}, + {x: "+Inf", inf: true}, + {x: "NaN", nan: true}, + } { + x := makeFloat(test.x) + if got := x.IsNeg(); got != test.neg { + t.Errorf("(%s).IsNeg() = %v; want %v", test.x, got, test.neg) + } + if got := x.IsZero(); got != test.zero { + t.Errorf("(%s).IsZero() = %v; want %v", test.x, got, test.zero) + } + if got := x.IsFinite(); got != test.finite { + t.Errorf("(%s).IsFinite() = %v; want %v", test.x, got, test.finite) + } + if got := x.IsInf(); got != test.inf { + t.Errorf("(%s).IsInf() = %v; want %v", test.x, got, test.inf) + } + if got := x.IsNaN(); got != test.nan { + t.Errorf("(%s).IsNaN() = %v; want %v", test.x, got, test.nan) + } + } +} + func TestFloatIsInt(t *testing.T) { for _, test := range []string{ "0 int", @@ -314,14 +346,6 @@ func TestFloatIsInt(t *testing.T) { } } -func TestFloatIsInf(t *testing.T) { - // TODO(gri) implement this -} - -func TestFloatIsNaN(t *testing.T) { - // TODO(gri) implement this -} - func fromBinary(s string) int64 { x, err := strconv.ParseInt(s, 2, 64) if err != nil { @@ -740,10 +764,6 @@ func TestFloatSetInf(t *testing.T) { } } -func TestFloatSetNaN(t *testing.T) { - // TODO(gri) implement -} - func TestFloatUint64(t *testing.T) { for _, test := range []struct { x string diff --git a/src/math/big/floatconv.go b/src/math/big/floatconv.go index 31e192f5b4..23cf948a02 100644 --- a/src/math/big/floatconv.go +++ b/src/math/big/floatconv.go @@ -63,7 +63,7 @@ func (z *Float) SetString(s string) (*Float, bool) { // be binary, if present (an "e" or "E" exponent indicator cannot be // distinguished from a mantissa digit). // -// BUG(gri) This signature conflicts with Scan(s fmt.ScanState, ch rune) error. +// BUG(gri) The Float.Scan signature conflicts with Scan(s fmt.ScanState, ch rune) error. func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) { if z.prec == 0 { z.prec = 64 @@ -224,7 +224,7 @@ func ParseFloat(s string, base int, prec uint, mode RoundingMode) (f *Float, b i // number of digits necessary such that ParseFloat will return f exactly. // The prec value is ignored for the 'b' or 'p' format. // -// BUG(gri) Currently, Format does not accept negative precisions. +// BUG(gri) Float.Format does not accept negative precisions. func (x *Float) Format(format byte, prec int) string { const extra = 10 // TODO(gri) determine a good/better value here return string(x.Append(make([]byte, 0, prec+extra), format, prec)) @@ -236,7 +236,7 @@ func (x *Float) Append(buf []byte, format byte, prec int) []byte { // TODO(gri) factor out handling of sign? // Inf - if x.IsInf(0) { + if x.IsInf() { var ch byte = '+' if x.neg { ch = '-' @@ -261,7 +261,7 @@ func (x *Float) Append(buf []byte, format byte, prec int) []byte { return x.bigFtoa(buf, format, prec) } -// BUG(gri): Currently, String uses x.Format('g', 10) rather than x.Format('g', -1). +// BUG(gri): Float.String uses x.Format('g', 10) rather than x.Format('g', -1). func (x *Float) String() string { return x.Format('g', 10) } diff --git a/src/math/big/floatexample_test.go b/src/math/big/floatexample_test.go index 460ef9c0a6..23655d0966 100644 --- a/src/math/big/floatexample_test.go +++ b/src/math/big/floatexample_test.go @@ -12,7 +12,7 @@ import ( // TODO(gri) add more examples func ExampleFloat_Add() { - // Operating on numbers of different precision is easy. + // Operating on numbers of different precision. var x, y, z big.Float x.SetInt64(1000) // x is automatically set to 64bit precision y.SetFloat64(2.718281828) // y is automatically set to 53bit precision @@ -22,9 +22,9 @@ func ExampleFloat_Add() { fmt.Printf("y = %s (%s, prec = %d, acc = %s)\n", &y, y.Format('p', 0), y.Prec(), y.Acc()) fmt.Printf("z = %s (%s, prec = %d, acc = %s)\n", &z, z.Format('p', 0), z.Prec(), z.Acc()) // Output: - // x = 1000 (0x.fap10, prec = 64, acc = exact) - // y = 2.718281828 (0x.adf85458248cd8p2, prec = 53, acc = exact) - // z = 1002.718282 (0x.faadf854p10, prec = 32, acc = below) + // x = 1000 (0x.fap10, prec = 64, acc = Exact) + // y = 2.718281828 (0x.adf85458248cd8p2, prec = 53, acc = Exact) + // z = 1002.718282 (0x.faadf854p10, prec = 32, acc = Below) } func Example_Shift() { diff --git a/src/math/big/roundingmode_string.go b/src/math/big/roundingmode_string.go new file mode 100644 index 0000000000..05024b8065 --- /dev/null +++ b/src/math/big/roundingmode_string.go @@ -0,0 +1,16 @@ +// generated by stringer -type=RoundingMode; DO NOT EDIT + +package big + +import "fmt" + +const _RoundingMode_name = "ToNearestEvenToNearestAwayToZeroAwayFromZeroToNegativeInfToPositiveInf" + +var _RoundingMode_index = [...]uint8{0, 13, 26, 32, 44, 57, 70} + +func (i RoundingMode) String() string { + if i+1 >= RoundingMode(len(_RoundingMode_index)) { + return fmt.Sprintf("RoundingMode(%d)", i) + } + return _RoundingMode_name[_RoundingMode_index[i]:_RoundingMode_index[i+1]] +} -- 2.48.1