From 7a675fc1a4b01f62e37d4403f29c8c224c303b83 Mon Sep 17 00:00:00 2001 From: Eric Lagergren Date: Wed, 29 May 2019 21:37:41 -0700 Subject: [PATCH 1/6] init --- big.go | 581 ++++++++++++++++++++++++++-------------- big_test.go | 105 ++++++++ doc.go | 118 ++++---- example_decimal_test.go | 2 + format_test.go | 89 ------ util.go | 6 +- 6 files changed, 536 insertions(+), 365 deletions(-) delete mode 100644 format_test.go diff --git a/big.go b/big.go index 3aa68bb..ce73ff6 100644 --- a/big.go +++ b/big.go @@ -43,7 +43,7 @@ type Big struct { // digits this field will be used. compact uint64 - // exp is the negated scale, meaning + // exp is the negated scale. This means // // number × 10**exp = number × 10**-scale // @@ -119,7 +119,7 @@ func (f form) String() string { } } -// Payload is a NaN value's payload. +// Payload is a NaN value's payload. A zero value indicates no payload. type Payload uint64 //go:generate stringer -type Payload -linecomment @@ -156,14 +156,227 @@ const ( remprec // result of remainder operation was larger than the desired precision ) -// An ErrNaN is used when a decimal operation would lead to a NaN under IEEE-754 -// rules. An ErrNaN implements the error interface. +// TODO(eric): if math.ErrNaN ever allows setting the msg field, perhaps we +// should use that instead? + +// An ErrNaN is as the value passed to ``panic'' when the operating mode is set +// to Go and a decimal operation occurs that would lead to a NaN under IEEE-754 +// rules. +// +// ErrNaN implements the error interface. type ErrNaN struct{ Msg string } func (e ErrNaN) Error() string { return e.Msg } var _ error = ErrNaN{} +// Append appends x to buf using the specified format and returns the resulting +// slice. +// +// The following verbs are recognized: +// +// 'e': -d.dddd±edd +// 'E': -d.dddd±Edd +// 'f': -dddd.dd +// 'F': same as 'f' +// 'g': same as 'f' or 'e', depending on x +// 'G': same as 'F' or 'E', depending on x +// 'z': same as 'g', but with trailing zeros and trailing decimal point +// 'Z': same as 'G', but with trailing zeros and trailing decimal point +// +// Note that 'z' and 'Z' are analogous to the ``fmt'' package's verb-flag +// combinations %#g and %#G, respectively. +// +// For every format except 'g' and 'G', the precision is the number of digits +// following the radix. For 'g' and 'G', however, the precision is the number of +// significant digits. +func (x *Big) Append(buf []byte, fmt byte, prec int) []byte { + if x == nil { + return append(buf, ""...) + } + + if x.isSpecial() { + switch x.Context.OperatingMode { + case GDA: + buf = append(buf, x.form.String()...) + if x.IsNaN(0) && x.compact != 0 { + buf = strconv.AppendUint(buf, x.compact, 10) + } + case Go: + if x.IsNaN(0) { + buf = append(buf, "NaN"...) + } else if x.IsInf(0) { + buf = append(buf, "+Inf"...) + } else { + buf = append(buf, "Inf"...) + } + default: + buf = append(buf, '?') + } + return buf + } + + neg := x.Signbit() + if neg { + buf = append(buf, '-') + } + + dec := make([]byte, 0, x.Precision()) + if x.isCompact() { + dec = strconv.AppendUint(dec[0:0], uint64(x.compact), 10) + } else { + dec = x.unscaled.Append(dec[0:0], 10) + } + + switch fmt { + case 'g', 'G', 'z', 'Z': + // "Next, the adjusted exponent is calculated; this is the exponent, plus + // the number of characters in the converted coefficient, less one. That + // is, exponent+(clength-1), where clength is the length of the coefficient + // in decimal digits. + adj := x.exp + (len(dec) - 1) + // "If the exponent is less than or equal to zero and the adjusted + // exponent is greater than or equal to -6 the number will be converted + // to a character form without using exponential notation." + // + // - http://speleotrove.com/decimal/daconvs.html#reftostr + if x.exp <= 0 && adj >= -6 { + if x.exp == 0 { + prec = 0 + } else { + prec += x.exp + } + const z = 'z' & 'Z' + return fmtF(buf, fmt&z != z, x.exp, prec, dec) + } + return fmtE(buf, fmt+'e'-'g', adj, prec, dec) + case 'e', 'E': + return fmtE(buf, fmt, x.exp+(len(dec)-1), prec, dec) + case 'f', 'F': + return fmtF(buf, false, x.exp, prec, dec) + default: + if neg { + buf = buf[:len(buf)-1] + } + return append(buf, '%', fmt) + } +} + +func fmtE(buf []byte, fmt byte, adj, prec int, dec []byte) []byte { + buf = append(buf, dec[0]) + if prec > 0 { + buf = append(buf, '.') + buf = appendPad(buf, dec[1:], prec) + } + buf = append(buf, fmt) + if adj >= 0 { + buf = append(buf, '+') + } else { + buf = append(buf, '-') + adj = -adj + } + if adj < 10 { + buf = append(buf, '0') // 01, 02, ..., 09 + } + return strconv.AppendUint(buf, uint64(adj), 10) // adj >= 0 +} + +func fmtF(buf []byte, strip bool, exp, prec int, dec []byte) []byte { + fmt.Printf("exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", exp, prec, exp+len(dec), len(dec), dec) + if exp >= 0 { + buf = append(buf, dec...) + buf = appendZeros(buf, exp-len(dec)) + if prec > exp { + buf = append(buf, '.') + buf = appendZeros(buf, prec-exp) + } + return buf + } + if radix := exp + len(dec); radix <= 0 { + buf = append(buf, "0."...) + m := min(-radix, prec) + buf = appendZeros(buf, m) + if m == 0 || m < prec { + buf = append(buf, dec...) + } + if n := -radix + len(dec); n < prec { + buf = appendZeros(buf, prec-n) + } + } else { + buf = append(buf, dec[:radix]...) + buf = append(buf, '.') + m := min(radix+prec, len(dec)) + fmt.Println("m=", m, "p=", radix+prec, "r=", radix, "p=", prec) + buf = append(buf, dec[radix:m]...) + if (m - radix) < prec { + buf = appendZeros(buf, prec-(m-radix)) + } + } + return buf + + fmt.Println("r=", len(dec)+exp, "e=", exp, "p=", prec, strip, `"`+string(dec)+`"`) + switch radix := len(dec) + exp; { + // 123[.456] + case radix > 0: + buf = append(buf, dec[:radix]...) + println(prec != 0, !strip, !allzero(dec[radix:])) + if prec != 0 && (!strip || !allzero(dec[radix:])) { + buf = append(buf, '.') + buf = appendPad(buf, dec[radix:], prec) + } + // 0.00000123456 + case radix < 0: + buf = append(buf, '0') + if min := min(-radix, prec); !strip || min >= prec { + buf = append(buf, '.') + buf = appendZeros(buf, min) + if min < prec { + buf = appendPad(buf, dec, prec-min) + } + } + // 0[.0000...] or 0.123456 + default: + buf = append(buf, "0."...) + buf = appendPad(buf, dec, prec) + } + return buf +} + +func allzero(p []byte) bool { + for _, c := range p { + if c != '0' { + return false + } + } + return true +} + +// appendPad appends x up to x[:prec] to buf, right-padding with '0's if +// len(x) < prec. +func appendPad(buf, x []byte, prec int) []byte { + if n := prec - len(x); n > 0 { + buf = append(buf, x...) + buf = appendZeros(buf, n) + } else { + buf = append(buf, x[:prec]...) + } + return buf +} + +// appeendZeros appends n '0's to buf. +func appendZeros(buf []byte, n int) []byte { + const zeros = "0000000000000000" + for n >= 0 { + if n < len(zeros) { + buf = append(buf, zeros[:n]...) + } else { + buf = append(buf, zeros...) + } + n -= len(zeros) + } + return buf +} + // CheckNaNs checks if either x or y is NaN. If so, it follows the rules of NaN // handling set forth in the GDA specification. The second argument, y, may be // nil. It returns true if either condition is a NaN. @@ -256,16 +469,16 @@ func (z *Big) Add(x, y *Big) *Big { return z.Context.Add(z, x, y) } // Class returns the ``class'' of x, which is one of the following: // -// sNaN -// NaN -// -Infinity -// -Normal -// -Subnormal -// -Zero -// +Zero -// +Subnormal -// +Normal -// +Infinity +// sNaN +// NaN +// -Infinity +// -Normal +// -Subnormal +// -Zero +// +Zero +// +Subnormal +// +Normal +// +Infinity // func (x *Big) Class() string { if x.IsNaN(0) { @@ -536,125 +749,52 @@ func (x *Big) Float(z *big.Float) *big.Float { return z } -// Format implements the fmt.Formatter interface. The following verbs are -// supported: +// Format implements the fmt.Formatter interface. // -// %s: -dddd.dd or -d.dddd±edd, depending on x -// %d: same as %s -// %v: same as %s -// %e: -d.dddd±edd -// %E: -d.dddd±Edd -// %f: -dddd.dd -// %g: same as %f +// Format recognizes the same format verbs as Append, as well as the following +// aliases for %g: // -// While width is honored in the same manner as the fmt package (the minimum -// width of the formatted number), precision is the number of significant digits -// in the decimal number. Given %f, however, precision is the number of digits -// following the radix. +// %s, %d, and %v // -// Format honors all flags (such as '+' and ' ') in the same manner as the fmt -// package, except for '#'. Unless used in conjunction with %v, %q, or %p, the -// '#' flag will be ignored; decimals have no defined hexadeximal or octal -// representation. +// Additionally, the verb %q is recognized as an alias of %s and is quoted +// appropriately. // -// %+v, %#v, %T, %#p, and %p all honor the formats specified in the fmt -// package's documentation. +// When used in conjunction with any of the recognized verbs, Format honors all +// flags in the manner described for floating point numbers in the``fmt'' package. +// +// The default precision for %e, %f, and %g is the decimal's current precision. +// +// All other unrecognized format and flag combinations (such as %#v) are passed +// through to the ``fmt'' package. func (x *Big) Format(s fmt.State, c rune) { if debug { x.validate() } - prec, hasPrec := s.Precision() - if !hasPrec { - prec = x.Precision() - } - width, hasWidth := s.Width() - if !hasWidth { - width = noWidth - } - - var ( - hash = s.Flag('#') - dash = s.Flag('-') - lpZero = s.Flag('0') - lpSpace = width != noWidth && !dash && !lpZero - plus = s.Flag('+') - space = s.Flag(' ') - f = formatter{prec: prec, width: width} - e = sciE[x.Context.OperatingMode] - ) - - // If we need to left pad then we need to first write our string into an - // empty buffer. - tmpbuf := lpZero || lpSpace - if tmpbuf { - b := new(strings.Builder) - b.Grow(x.Precision()) - f.w = b - } else { - f.w = stateWrapper{s} - } - - if plus { - f.sign = '+' - } else if space { - f.sign = ' ' - } - - // noE is a placeholder for formats that do not use scientific notation - // and don't require 'e' or 'E' - const noE = 0 + var quote string // either `"` or "" switch c { - case 's', 'd': - f.format(x, normal, e) - case 'q': - // The fmt package's docs specify that the '+' flag - // "guarantee[s] ASCII-only output for %q (%+q)" - f.sign = 0 - - // Since no other escaping is needed we can do it ourselves and save - // whatever overhead running it through fmt.Fprintf would incur. - quote := byte('"') - if hash { - quote = '`' + case 'e', 'E', 'f', 'F': + // OK + case 'g', 'G': + if s.Flag('#') { + c += 'e' - 'g' } - f.WriteByte(quote) - f.format(x, normal, e) - f.WriteByte(quote) - case 'e', 'E': - f.format(x, sci, byte(c)) - case 'f', 'F': - if !hasPrec { - prec = 0 - } else { - // %f's precision means "number of digits after the radix" - if x.exp > 0 { - f.prec += x.Precision() - } else { - if adj := x.exp + x.Precision(); adj > -f.prec { - f.prec += adj - } else { - f.prec = -f.prec - } - } + case 'q': + quote = `"` + if s.Flag('#') { + quote = "`" } - - f.format(x, plain, noE) - case 'g', 'G': - // %g's precision means "number of significant digits" - f.format(x, plain, noE) - - // Make sure we return from the following two cases. + c = 'g' + case 's', 'd': + c = 'g' case 'v': - // %v == %s - if !hash && !plus { - f.format(x, normal, e) + if !s.Flag('#') { + c = 'g' break } - - // This is the easiest way of doing it. Note we can't use type Big Big, - // even though it's declared inside a function. Go thinks it's recursive. - // At least the fields are checked at compile time. + // Handle %#v specially. + fallthrough + default: type Big struct { Context Context unscaled big.Int @@ -663,49 +803,108 @@ func (x *Big) Format(s fmt.State, c rune) { precision int form form } - specs := "" - if dash { - specs += "-" - } else if lpZero { - specs += "0" - } - if hash { - specs += "#" - } else if plus { - specs += "+" - } else if space { - specs += " " - } - fmt.Fprintf(s, "%"+specs+"v", (*Big)(x)) + fmt.Fprintf(s, makeFormat(s, c), (*Big)(x)) return + } + + prec, ok := s.Precision() + if !ok { + prec = x.Precision() + } + buf := make([]byte, 0, x.Precision()+1) + buf = x.Append(buf, byte(c), prec) + + var sign string + switch { + case buf[0] == '-': + sign = "-" + buf = buf[1:] + case buf[0] == '+': + // +Inf + sign = "+" + if s.Flag(' ') { + sign = " " + } + buf = buf[1:] + case s.Flag('+'): + sign = "+" + case s.Flag(' '): + sign = " " + } + + var padding int + if width, ok := s.Width(); ok && width > len(sign)+len(buf) { + padding = width - len(sign) - len(buf) + } + + switch { + case s.Flag('0') && !x.IsInf(0): + writeN(s, sign, 1) + writeN(s, "0", padding) + writeN(s, quote, 1) + s.Write(buf) + writeN(s, quote, 1) + case s.Flag('-'): + writeN(s, sign, 1) + writeN(s, quote, 1) + s.Write(buf) + writeN(s, quote, 1) + writeN(s, " ", padding) default: - fmt.Fprintf(s, "%%!%c(*decimal.Big=%s)", c, x.String()) - return + writeN(s, " ", padding) + writeN(s, sign, 1) + writeN(s, quote, 1) + s.Write(buf) + writeN(s, quote, 1) } +} - // Need padding out to width. - if f.n < int64(width) { - switch pad := int64(width) - f.n; { - case dash: - io.CopyN(s, spaceReader{}, pad) - case lpZero: - io.CopyN(s, zeroReader{}, pad) - case lpSpace: - io.CopyN(s, spaceReader{}, pad) +// makeFormat recreates a format string. +func makeFormat(s fmt.State, c rune) string { + var b strings.Builder + b.WriteByte('%') + for _, c := range "+-# 0" { + if s.Flag(int(c)) { + b.WriteRune(c) } } + if width, ok := s.Width(); ok { + b.WriteString(strconv.Itoa(width)) + } + if prec, ok := s.Precision(); ok { + b.WriteByte('.') + b.WriteString(strconv.Itoa(prec)) + } + b.WriteRune(c) + return b.String() +} - if tmpbuf { - // fmt's internal state type implements stringWriter I think. - io.WriteString(s, f.w.(*strings.Builder).String()) +// writeN writes s to w n times, if s != "". +func writeN(w io.Writer, s string, n int) { + if s == "" { + return + } + type stringWriter interface { + WriteString(string) (int, error) + } + if sw, ok := w.(stringWriter); ok { + for ; n > 0; n-- { + sw.WriteString(s) + } + } else { + tmp := []byte(s) + for ; n > 0; n-- { + w.Write(tmp) + } } } // FMA sets z to (x * y) + u without any intermediate rounding. func (z *Big) FMA(x, y, u *Big) *Big { return z.Context.FMA(z, x, y, u) } -// Int sets z to x, truncating the fractional portion (if any) and returns z. z -// is allowed to be nil. If x is an infinity or a NaN value the result is +// Int sets z to x, truncating the fractional portion (if any) and returns z. +// +// z is allowed to be nil. If x is an infinity or a NaN value the result is // undefined. func (x *Big) Int(z *big.Int) *big.Int { if debug { @@ -808,17 +1007,21 @@ func (x *Big) IsSubnormal() bool { } // IsInf returns true if 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. +// +// 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 *Big) IsInf(sign int) bool { return sign >= 0 && x.form == pinf || sign <= 0 && x.form == ninf } // IsNaN returns true if x is NaN. -// If sign > 0, IsNaN reports whether x is quiet NaN. -// If sign < 0, IsNaN reports whether x is signaling NaN. -// If sign == 0, IsNaN reports whether x is either NaN. +// +// If sign > 0, IsNaN reports whether x is quiet NaN. +// If sign < 0, IsNaN reports whether x is signaling NaN. +// If sign == 0, IsNaN reports whether x is either quiet or signaling NaN. +// func (x *Big) IsNaN(quiet int) bool { return quiet >= 0 && x.form&qnan == qnan || quiet <= 0 && x.form&snan == snan } @@ -842,14 +1045,14 @@ func (x *Big) IsInt() bool { xp := x.Precision() exp := x.exp - // 0.001 - // 0.5 + // 0.00d + // 0.d if -exp >= xp { return false } - // 44.00 - // 1.000 + // 44.dd + // 1.ddd if x.isCompact() { for v := x.compact; v%10 == 0; v /= 10 { exp++ @@ -875,17 +1078,7 @@ func (x *Big) MarshalText() ([]byte, error) { if debug { x.validate() } - if x == nil { - return []byte(""), nil - } - var ( - b = new(bytes.Buffer) - f = formatter{w: b, prec: x.Precision(), width: noWidth} - e = sciE[x.Context.OperatingMode] - ) - b.Grow(x.Precision()) - f.format(x, normal, e) - return b.Bytes(), nil + return x.Append(nil, 's', -1), nil } // Mul sets z to x * y and returns z. @@ -910,11 +1103,11 @@ func (z *Big) Neg(x *Big) *Big { // New creates a new Big decimal with the given value and scale. For example: // -// New(1234, 3) // 1.234 -// New(42, 0) // 42 -// New(4321, 5) // 0.04321 -// New(-1, 0) // -1 -// New(3, -10) // 30 000 000 000 +// New(1234, 3) // 1.234 +// New(42, 0) // 42 +// New(4321, 5) // 0.04321 +// New(-1, 0) // -1 +// New(3, -10) // 30 000 000 000 // func New(value int64, scale int) *Big { return new(Big).SetMantScale(value, scale) @@ -961,8 +1154,10 @@ func (z *Big) QuoRem(x, y, r *Big) (*Big, *Big) { return z.Context.QuoRem(z, x, y, r) } -// Rat sets z to x and returns z. z is allowed to be nil. The result is undefined if -// x is an infinity or NaN value. +// Rat sets z to x and returns z. +// +// z is allowed to be nil. The result is undefined if x is an infinity or NaN +// value. func (x *Big) Rat(z *big.Rat) *big.Rat { if debug { x.validate() @@ -1010,12 +1205,14 @@ func (x *Big) Rat(z *big.Rat) *big.Rat { return z.SetFrac(num, denom) } -// Raw directly returns x's raw compact and unscaled values. Caveat emptor: -// Neither are guaranteed to be valid. Raw is intended to support missing -// functionality outside this package and generally should be avoided. +// Raw directly returns x's raw compact and unscaled values. +// +// Caveat emptor: Neither are guaranteed to be valid. Raw is intended to support +// missing functionality outside this package and generally should be avoided. +// // Additionally, Raw is the only part of this package's API which is not -// guaranteed to remain stable. This means the function could change or -// disappear at any time, even across minor version numbers. +// guaranteed to remain stable. This means the function could change or disappear +// without warning at any time, even across minor versions. func Raw(x *Big) (*uint64, *big.Int) { return &x.compact, &x.unscaled } // Reduce reduces a finite z to its most simplest form. @@ -1373,17 +1570,7 @@ func (x *Big) Signbit() bool { // discussed in the Format method's documentation. Special cases depend on the // OperatingMode. func (x *Big) String() string { - if x == nil { - return "" - } - var ( - b = new(strings.Builder) - f = formatter{w: b, prec: x.Precision(), width: noWidth} - e = sciE[x.Context.OperatingMode] - ) - b.Grow(x.Precision()) - f.format(x, normal, e) - return b.String() + return string(x.Append(nil, 's', -1)) } // Sub sets z to x - y and returns z. @@ -1411,15 +1598,7 @@ func (x *Big) validate() { if caller := runtime.FuncForPC(pc); ok && caller != nil { fmt.Println("called by:", caller.Name()) } - type Big struct { - Context Context - unscaled big.Int - compact uint64 - exp int - precision int - form form - } - fmt.Printf("%#v\n", (*Big)(x)) + println(x) panic(err) } }() diff --git a/big_test.go b/big_test.go index e969989..c05e965 100644 --- a/big_test.go +++ b/big_test.go @@ -1,6 +1,7 @@ package decimal_test import ( + "fmt" "math" "math/big" "math/rand" @@ -77,6 +78,110 @@ func TestBig_Float(t *testing.T) { } } +// TestDecimal_Format tests Decimal.Format. The test cases are largely taken +// from the fmt package's test cases. +func TestDecimal_Format(t *testing.T) { + for i, s := range [...]struct { + format string + input string + want string + }{ + {"%s", ".12", "0.12"}, + {"%s", "12", "12"}, + {"%.5g", "1", "1"}, + {"%s", "12.34", "12.34"}, + {"%.3g", "12.34", "12.3"}, + {"'%5.2f'", "0.", "' 0.00'"}, + {"%.10f", "0.1234567891", "0.1234567891"}, + {"%.10f", "0.01", "0.0100000000"}, + {"%.10f", "0.0000000000000000000000000000000000000000000000000000000000001", "0.0000000000"}, + {"%+.3e", "0.0", "+0.000e-01"}, // +00 -> -01 + {"%+.3e", "1.0", "+1.000e+00"}, + {"%+.3f", "-1.0", "-1.000"}, + {"%+.3F", "-1.0", "-1.000"}, + {"%+07.2f", "1.0", "+001.00"}, + {"%+07.2f", "-1.0", "-001.00"}, + {"%-07.2f", "1.0", "1.00 "}, + {"%-07.2f", "-1.0", "-1.00 "}, + {"%+-07.2f", "1.0", "+1.00 "}, + {"%+-07.2f", "-1.0", "-1.00 "}, + {"%-+07.2f", "1.0", "+1.00 "}, + {"%-+07.2f", "-1.0", "-1.00 "}, + {"%+10.2f", "+1.0", " +1.00"}, + {"%+10.2f", "-1.0", " -1.00"}, + {"% .3E", "-1.0", "-1.000E+00"}, + {"% .3e", "1.0", " 1.000e+00"}, + {"%+.3g", "0.0", "+0.0"}, // += .0 + {"%+.3g", "1.0", "+1"}, + {"%+.3g", "-1.0", "-1"}, + {"% .3g", "-1.0", "-1"}, + {"% .3g", "1.0", " 1"}, + // Test sharp flag used with floats. + // TODO(eric): add these if we honor the '#' flag. + // {"%#g", "1e-323", "1.00000e-323"}, + // {"%#g", "-1.0", "-1.00000"}, + // {"%#g", "1.1", "1.10000"}, + // {"%#g", "123456.0", "123456."}, + // {"%#g", "1234567.0", "1.234567e+06"}, + // {"%#g", "1230000.0", "1.23000e+06"}, + // {"%#g", "1000000.0", "1.00000e+06"}, + // {"%#.0f", "1.0", "1."}, + // {"%#.0e", "1.0", "1.e+00"}, + // {"%#.0g", "1.0", "1."}, + // {"%#.0g", "1100000.0", "1.e+06"}, + // {"%#.4f", "1.0", "1.0000"}, + // {"%#.4e", "1.0", "1.0000e+00"}, + // {"%#.4g", "1.0", "1.000"}, + // {"%#.4g", "100000.0", "1.000e+05"}, + // {"%#.0f", "123.0", "123."}, + // {"%#.0e", "123.0", "1.e+02"}, + // {"%#.0g", "123.0", "1.e+02"}, + // {"%#.4f", "123.0", "123.0000"}, + // {"%#.4e", "123.0", "1.2300e+02"}, + // {"%#.4g", "123.0", "123.0"}, + // {"%#.4g", "123000.0", "1.230e+05"}, + // {"%#9.4g", "1.0", " 1.000"}, + // Test correct f.intbuf boundary checks. + {"%.68f", "1.0", zeroFill("1.", 68, "")}, + {"%.68f", "-1.0", zeroFill("-1.", 68, "")}, + // float infinites and NaNs + {"%f", "+Inf", "Infinity"}, + {"%.1f", "-Inf", "-Infinity"}, + {"% f", "NaN", " NaN"}, + {"%20f", "+Inf", " Infinity"}, + {"% 20F", "+Inf", " Infinity"}, + {"% 20e", "-Inf", " -Infinity"}, + {"%+20E", "-Inf", " -Infinity"}, + {"% +20g", "-Inf", " -Infinity"}, + {"%+-20G", "+Inf", "+Infinity "}, + {"%20e", "NaN", " NaN"}, + {"% +20E", "NaN", " +NaN"}, + {"% -20g", "NaN", " NaN "}, + {"%+-20G", "NaN", "+NaN "}, + // Zero padding does not apply to infinities and NaN. + {"%+020e", "+Inf", " +Infinity"}, + {"%-020f", "-Inf", "-Infinity "}, + {"%-020E", "NaN", "NaN "}, + } { + t.Run(strconv.Itoa(i), func(t *testing.T) { + z, _ := new(decimal.Big).SetString(s.input) + got := fmt.Sprintf(s.format, z) + if got != s.want { + t.Fatalf(`#%d: printf("%s", "%s") +got : %q +wanted: %q +`, i, s.format, s.input, got, s.want) + } + }) + } +} + +// zeroFill generates zero-filled strings of the specified width. The length +// of the suffix (but not the prefix) is compensated for in the width calculation. +func zeroFill(prefix string, width int, suffix string) string { + return prefix + strings.Repeat("0", width-len(suffix)) + suffix +} + func TestBig_Int(t *testing.T) { for i, test := range randDecs { a, ok := new(decimal.Big).SetString(test) diff --git a/doc.go b/doc.go index 82a3a9a..4bac2f2 100644 --- a/doc.go +++ b/doc.go @@ -1,65 +1,18 @@ -// Package decimal provides a high-performance, arbitrary precision, -// floating-point decimal library. +// Package decimal implements arbitrary precision, decimal floating-point numbers. // // Overview // -// This package provides floating-point decimal numbers, useful for financial -// programming or calculations where a larger, more accurate representation of -// a number is required. +// Decimal implements decimals according to the General Decimal Arithmetic[0] +// (GDA) specification, version 1.70. // -// In addition to basic arithmetic operations (addition, subtraction, -// multiplication, and division) this package offers various mathematical -// functions, including the exponential function, various logarithms, and the -// ability to compute continued fractions. -// -// While lean, this package is full of features. It implements interfaces like -// ``fmt.Formatter'' and intuitively utilizes verbs and flags as described in -// the ``fmt'' package. (Also included: ``fmt.Scanner'', ``fmt.Stringer'', -// ``encoding.TextUnmarshaler'', and ``encoding.TextMarshaler''.) -// -// It allows users to specific explicit contexts for arithmetic operations, but -// doesn't require it. It provides access to NaN payloads and is more lenient -// when parsing a decimal from a string than the GDA specification requires. -// -// API interfaces have been changed slightly to work more seamlessly with -// existing Go programs. For example, many ``Quantize'' implementations require -// a decimal as both the receiver and argument which isn't very user friendly. -// Instead, this library accepts a simple ``int'' which can be derived from an -// existing decimal if required. -// -// It contains two modes of operation designed to make transitioning to various -// GDA "quirks" (like always rounding lossless operations) easier. -// -// GDA: strictly adhere to the GDA specification (default) -// Go: utilize Go idioms, more flexibility -// -// Goals -// -// There are three primary goals of this library: -// -// 1. Correctness -// -// By adhering to the General Decimal Arithmetic specification, this package -// has a well-defined structure for its arithmetic operations. -// -// 2. Performance -// -// Decimal libraries are inherently slow; this library works diligently to -// minimize memory allocations and utilize efficient algorithms. Performance -// regularly benchmarks as fast or faster than many other popular decimal -// libraries. -// -// 3. Ease of use -// -// Libraries should be intuitive and work out of the box without having to -// configure too many settings; however, precise settings should still be -// available. +// Decimal arithmetic is useful for financial programming or calculations (like +// in CAD) where larger, more accurate representations of numbers are required. // // Usage // // The following type is supported: // -// Big decimal numbers +// Big (arbitrary precision) decimal numbers // // The zero value for a Big corresponds with 0, meaning all the following are // valid: @@ -68,19 +21,15 @@ // y := new(Big) // z := &Big{} // -// Method naming is the same as math/big's, meaning: +// Method naming is the same as the ``math/big'' package', meaning: // -// func (z *T) SetV(v V) *T // z = v -// func (z *T) Unary(x *T) *T // z = unary x -// func (z *T) Binary(x, y *T) *T // z = x binary y -// func (x *T) Pred() P // p = pred(x) +// func (z *T) SetV(v V) *T // z = v +// func (z *T) Unary(x *T) *T // z = unary x +// func (z *T) Binary(x, y *T) *T // z = x binary y +// func (z *T) Ternary(x, y, u *T) *T // z = x ternary y ternary u +// func (x *T) Pred() P // p = pred(x) // -// In general, its conventions mirror math/big's. It is suggested to read the -// math/big package comments to gain an understanding of this package's -// conventions. -// -// Arguments to Binary and Unary methods are allowed to alias, so the following -// is valid: +// Arguments are allowed to alias, meaning the following is valid: // // x := New(1, 0) // x.Add(x, x) // x == 2 @@ -89,20 +38,45 @@ // y.FMA(y, x, y) // y == 3 // // Unless otherwise specified, the only argument that will be modified is the -// result (``z''). This means the following is valid and race-free: +// result, typically a receiver named ``z''. This means the following is valid +// and race-free: // // x := New(1, 0) -// var g1, g2 Big +// var z1, z2 Big // -// go func() { g1.Add(x, x) }() -// go func() { g2.Add(x, x) }() +// go func() { z1.Add(x, x) }() +// go func() { z2.Add(x, x) }() // -// But this is not: +// However, this is not: // // x := New(1, 0) -// var g Big +// var z Big +// +// go func() { z.Add(x, x) }() // BAD! RACE CONDITION! +// go func() { z.Add(x, x) }() // BAD! RACE CONDITION! +// +// Go's conventions differ from the GDA specification in a few key areas. For +// example, ``math/big.Float'' panics on NaN values, while the GDA specification +// does not require the exception created by the NaN value to be trapped. +// +// Because of this, there are two operating modes that allow users to better +// select their desired behavior: +// +// GDA: strictly adhere to the GDA specification (default) +// Go: utilize Go idioms, more flexibility +// +// Users can specify explicit contexts for arithmetic operations, and though +// while recommended, this is not required. Decimal also provides access to NaN +// payloads and is more lenient when parsing a decimal from a string than the +// GDA specification requires. +// +// In addition to basic arithmetic operations (addition, subtraction, +// multiplication, and division) this package offers a variety of mathematical +// functions, including the logarithms and continued fractions. (See: +// ``decimal/math''.) +// +// References: // -// go func() { g.Add(x, x) }() // BAD! RACE CONDITION! -// go func() { g.Add(x, x) }() // BAD! RACE CONDITION! +// - [0]: http://speleotrove.com/decimal/decarith.html // package decimal diff --git a/example_decimal_test.go b/example_decimal_test.go index f32fc4f..6743447 100644 --- a/example_decimal_test.go +++ b/example_decimal_test.go @@ -13,12 +13,14 @@ func ExampleBig_Format() { } print("%s", "12.34") + print("%q", "12.34") print("%.3g", "12.34") print("%.1f", "12.34") print("`%6.4g`", "500.44") print("'%-10.f'", "-404.040") // Output: // 12.34 + // "12.34" // 12.3 // 12.3 // ` 500.4` diff --git a/format_test.go b/format_test.go deleted file mode 100644 index 3c643a1..0000000 --- a/format_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package decimal - -import ( - "fmt" - "testing" -) - -func TestRoundString(t *testing.T) { - type roundStringTest struct { - input string - mode RoundingMode - prec int - expect string - } - - // From https://en.wikipedia.org/wiki/IEEE_floating_point#Rounding_rules - // Formatted with https://ozh.github.io/ascii-tables/ - // - // +---------------------------------+-------+-------+-------+-------+ - // | Mode / Example Value | +11.5 | +12.5 | −11.5 | −12.5 | - // +---------------------------------+-------+-------+-------+-------+ - // | to nearest, ties to even | +12.0 | +12.0 | −12.0 | −12.0 | - // | to nearest, ties away from zero | +12.0 | +13.0 | −12.0 | −13.0 | - // | toward 0 | +11.0 | +12.0 | −11.0 | −12.0 | - // | toward +∞ | +12.0 | +13.0 | −11.0 | −12.0 | - // | toward −∞ | +11.0 | +12.0 | −12.0 | −13.0 | - // +---------------------------------+-------+-------+-------+-------+ - - makeWikiTests := func(mode RoundingMode, out ...string) []roundStringTest { - var tests [4]roundStringTest - for i, inp := range [...]string{"+115", "+125", "-115", "-125"} { - tests[i] = roundStringTest{inp, mode, 2, out[i]} - } - return tests[:] - } - - even := makeWikiTests(ToNearestEven, "12", "12", "12", "12") - away := makeWikiTests(ToNearestAway, "12", "13", "12", "13") - zero := makeWikiTests(ToZero, "11", "12", "11", "12") - pinf := makeWikiTests(ToPositiveInf, "12", "13", "11", "12") - ninf := makeWikiTests(ToNegativeInf, "11", "12", "12", "13") - - tests := []roundStringTest{ - {"+12345", ToNearestEven, 4, "1234"}, - {"+12349", ToNearestEven, 4, "1235"}, - {"+12395", ToNearestEven, 4, "1240"}, - {"+99", ToNearestEven, 1, "10"}, - {"+400", ToZero /* mode is irrelevant */, 1, "4"}, - } - tests = append(tests, even...) - tests = append(tests, away...) - tests = append(tests, zero...) - tests = append(tests, pinf...) - tests = append(tests, ninf...) - - for i, test := range tests { - pos := test.input[0] == '+' - inp := test.input[1:] - got := roundString([]byte(inp), test.mode, pos, test.prec) - if string(got) != test.expect { - t.Fatalf(`#%d: -[round(%q, %s, %d)] -got : %q -wanted: %q -`, i, test.input, test.mode, test.prec, got, test.expect) - } - } -} - -func TestDecimal_Format(t *testing.T) { - for i, s := range [...]struct { - format string - input string - want string - }{ - {"%.10f", "0.1234567891", "0.1234567891"}, - {"%.10f", "0.01", "0.0100000000"}, - {"%.10f", "0.0000000000000000000000000000000000000000000000000000000000001", "0.0000000000"}, - } { - z, _ := new(Big).SetString(s.input) - got := fmt.Sprintf(s.format, z) - if got != s.want { - t.Fatalf(`#%d: printf(%s) -got : %s -wanted: %s -`, i, s.format, got, s.want) - } - } -} diff --git a/util.go b/util.go index 3ea7111..2172416 100644 --- a/util.go +++ b/util.go @@ -193,8 +193,8 @@ func bigScalex(z, x *big.Int, scale int) *big.Int { } func min(x, y int) int { - if x < y { - return x + if y < x { + return y } - return y + return x } From 72df8e4ff207992a36b400ea6760256bd55a2bb1 Mon Sep 17 00:00:00 2001 From: Eric Lagergren Date: Thu, 25 Jul 2019 19:01:42 -0700 Subject: [PATCH 2/6] update AUTHORS --- AUTHORS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 82f00b5..71fce38 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,3 +9,5 @@ Nathan Hack fantomgs Timothy Ham Richard Dingwall +Yarco Hayduk +Daniel Theophanes From b30e006c21f0913102d83845717e21e19b576dae Mon Sep 17 00:00:00 2001 From: Eric Lagergren Date: Sun, 13 Oct 2019 02:12:59 -0700 Subject: [PATCH 3/6] Go 1.13 migrations --- big.go | 18 +- big_ctx.go | 9 +- big_test.go | 65 ++++--- decomposer.go | 126 +++++++------ format.go | 347 ----------------------------------- format_string.go | 25 --- internal/arith/abs.go | 7 +- internal/arith/arith.go | 17 +- internal/arith/arith_1_11.go | 75 -------- internal/arith/arith_1_12.go | 35 ---- math/cosine.go | 10 +- math/tangent.go | 4 +- 12 files changed, 151 insertions(+), 587 deletions(-) delete mode 100644 format.go delete mode 100644 format_string.go delete mode 100644 internal/arith/arith_1_11.go delete mode 100644 internal/arith/arith_1_12.go diff --git a/big.go b/big.go index 00c9dee..aa64d8f 100644 --- a/big.go +++ b/big.go @@ -63,6 +63,7 @@ var ( _ fmt.Stringer = (*Big)(nil) _ json.Unmarshaler = (*Big)(nil) _ encoding.TextUnmarshaler = (*Big)(nil) + _ decomposer = (*Big)(nil) ) // form indicates whether a decimal is a finite number, an infinity, or a nan @@ -185,10 +186,12 @@ var _ error = ErrNaN{} // 'Z': same as 'G', but with trailing zeros and trailing decimal point // // Note that 'z' and 'Z' are analogous to the ``fmt'' package's verb-flag -// combinations %#g and %#G, respectively. +// combinations %#g and %#G, respectively. Because they're non-standard, 'z' and +// 'Z' may be removed or have their meaning changed depending on changes made to +// the ``fmt'' package. // -// For every format except 'g' and 'G', the precision is the number of digits -// following the radix. For 'g' and 'G', however, the precision is the number of +// For every format the precision is the number of digits following the radix, +// except in the case of 'g' and 'G' where the precision is the number of // significant digits. func (x *Big) Append(buf []byte, fmt byte, prec int) []byte { if x == nil { @@ -282,7 +285,9 @@ func fmtE(buf []byte, fmt byte, adj, prec int, dec []byte) []byte { } func fmtF(buf []byte, strip bool, exp, prec int, dec []byte) []byte { - fmt.Printf("exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", exp, prec, exp+len(dec), len(dec), dec) + fmt.Printf("exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", + exp, prec, exp+len(dec), len(dec), dec) + if exp >= 0 { buf = append(buf, dec...) buf = appendZeros(buf, exp-len(dec)) @@ -760,7 +765,8 @@ func (x *Big) Float(z *big.Float) *big.Float { // appropriately. // // When used in conjunction with any of the recognized verbs, Format honors all -// flags in the manner described for floating point numbers in the``fmt'' package. +// flags in the manner described for floating point numbers in the ``fmt'' +// package. // // The default precision for %e, %f, and %g is the decimal's current precision. // @@ -788,11 +794,11 @@ func (x *Big) Format(s fmt.State, c rune) { case 's', 'd': c = 'g' case 'v': + // Handle %#v as a special case. if !s.Flag('#') { c = 'g' break } - // Handle %#v specially. fallthrough default: type Big struct { diff --git a/big_ctx.go b/big_ctx.go index e12a678..375957b 100644 --- a/big_ctx.go +++ b/big_ctx.go @@ -3,6 +3,7 @@ package decimal import ( "math" "math/big" + "math/bits" "github.com/ericlagergren/decimal/internal/arith" cst "github.com/ericlagergren/decimal/internal/c" @@ -167,7 +168,7 @@ func (c Context) addCompact(z *Big, X0 uint64, Xsign form, Y uint64, Ysign form, // If the signs are the same, then X + Y = ℤ≠0. if Ysign == Xsign { - if sum, c := arith.Add64(X, Y); c == 0 { + if sum, c := bits.Add64(X, Y, 0); c == 0 { z.compact = sum if sum == cst.Inflated { z.unscaled.SetUint64(cst.Inflated) @@ -184,7 +185,7 @@ func (c Context) addCompact(z *Big, X0 uint64, Xsign form, Y uint64, Ysign form, sign := Xsign // X + (-Y) == X - Y == -(Y - X) // (-X) + Y == Y - X == -(X - Y) - diff, b := arith.Sub64(X, Y) + diff, b := bits.Sub64(X, Y, 0) if b != 0 { sign ^= signbit diff = Y - X @@ -328,7 +329,7 @@ func (c Context) mul(z, x, y *Big) *Big { // Multiplication is simple, so inline it. if x.isCompact() { if y.isCompact() { - hi, lo := arith.Mul64(x.compact, y.compact) + hi, lo := bits.Mul64(x.compact, y.compact) if hi == 0 { z.compact = lo if lo == cst.Inflated { @@ -588,7 +589,7 @@ func (z *Big) quo(m RoundingMode, x uint64, xneg form, y uint64, yneg form) bool } rc := 1 - if hi, lo := arith.Mul64(r, 2); hi == 0 { + if hi, lo := bits.Mul64(r, 2); hi == 0 { rc = arith.Cmp(lo, y) } diff --git a/big_test.go b/big_test.go index 8c56d46..5e3b53a 100644 --- a/big_test.go +++ b/big_test.go @@ -81,7 +81,7 @@ func TestBig_Float(t *testing.T) { // TestDecimal_Format tests Decimal.Format. The test cases are largely taken // from the fmt package's test cases. func TestDecimal_Format(t *testing.T) { - for i, s := range [...]struct { + for i, s := range []struct { format string input string want string @@ -116,32 +116,29 @@ func TestDecimal_Format(t *testing.T) { {"%+.3g", "-1.0", "-1"}, {"% .3g", "-1.0", "-1"}, {"% .3g", "1.0", " 1"}, - // Test sharp flag used with floats. - // TODO(eric): add these if we honor the '#' flag. - // {"%#g", "1e-323", "1.00000e-323"}, - // {"%#g", "-1.0", "-1.00000"}, - // {"%#g", "1.1", "1.10000"}, - // {"%#g", "123456.0", "123456."}, - // {"%#g", "1234567.0", "1.234567e+06"}, - // {"%#g", "1230000.0", "1.23000e+06"}, - // {"%#g", "1000000.0", "1.00000e+06"}, - // {"%#.0f", "1.0", "1."}, - // {"%#.0e", "1.0", "1.e+00"}, - // {"%#.0g", "1.0", "1."}, - // {"%#.0g", "1100000.0", "1.e+06"}, - // {"%#.4f", "1.0", "1.0000"}, - // {"%#.4e", "1.0", "1.0000e+00"}, - // {"%#.4g", "1.0", "1.000"}, - // {"%#.4g", "100000.0", "1.000e+05"}, - // {"%#.0f", "123.0", "123."}, - // {"%#.0e", "123.0", "1.e+02"}, - // {"%#.0g", "123.0", "1.e+02"}, - // {"%#.4f", "123.0", "123.0000"}, - // {"%#.4e", "123.0", "1.2300e+02"}, - // {"%#.4g", "123.0", "123.0"}, - // {"%#.4g", "123000.0", "1.230e+05"}, - // {"%#9.4g", "1.0", " 1.000"}, - // Test correct f.intbuf boundary checks. + {"%#g", "1e-323", "1.00000e-323"}, + {"%#g", "-1.0", "-1.00000"}, + {"%#g", "1.1", "1.10000"}, + {"%#g", "123456.0", "123456."}, + {"%#g", "1234567.0", "1.234567e+06"}, + {"%#g", "1230000.0", "1.23000e+06"}, + {"%#g", "1000000.0", "1.00000e+06"}, + {"%#.0f", "1.0", "1."}, + {"%#.0e", "1.0", "1.e+00"}, + {"%#.0g", "1.0", "1."}, + {"%#.0g", "1100000.0", "1.e+06"}, + {"%#.4f", "1.0", "1.0000"}, + {"%#.4e", "1.0", "1.0000e+00"}, + {"%#.4g", "1.0", "1.000"}, + {"%#.4g", "100000.0", "1.000e+05"}, + {"%#.0f", "123.0", "123."}, + {"%#.0e", "123.0", "1.e+02"}, + {"%#.0g", "123.0", "1.e+02"}, + {"%#.4f", "123.0", "123.0000"}, + {"%#.4e", "123.0", "1.2300e+02"}, + {"%#.4g", "123.0", "123.0"}, + {"%#.4g", "123000.0", "1.230e+05"}, + {"%#9.4g", "1.0", " 1.000"}, {"%.68f", "1.0", zeroFill("1.", 68, "")}, {"%.68f", "-1.0", zeroFill("-1.", 68, "")}, // float infinites and NaNs @@ -163,16 +160,16 @@ func TestDecimal_Format(t *testing.T) { {"%-020f", "-Inf", "-Infinity "}, {"%-020E", "NaN", "NaN "}, } { - t.Run(strconv.Itoa(i), func(t *testing.T) { - z, _ := new(decimal.Big).SetString(s.input) - got := fmt.Sprintf(s.format, z) - if got != s.want { - t.Fatalf(`#%d: printf("%s", "%s") + //t.Run(strconv.Itoa(i), func(t *testing.T) { + z, _ := new(decimal.Big).SetString(s.input) + got := fmt.Sprintf(s.format, z) + if got != s.want { + t.Fatalf(`#%d: printf("%s", "%s") got : %q wanted: %q `, i, s.format, s.input, got, s.want) - } - }) + } + //}) } } diff --git a/decomposer.go b/decomposer.go index d2b1d48..a115e5e 100644 --- a/decomposer.go +++ b/decomposer.go @@ -4,93 +4,115 @@ import ( "encoding/binary" "fmt" "math" - "math/big" ) -// decomposer composes or decomposes a decimal value to and from individual parts. -// There are four separate parts: a boolean negative flag, a form byte with three possible states -// (finite=0, infinite=1, NaN=2), a base-2 big-endian integer -// coefficient (also known as a significand) as a []byte, and an int32 exponent. -// These are composed into a final value as "decimal = (neg) (form=finite) coefficient * 10 ^ exponent". -// A zero length coefficient is a zero value. +// decomposer composes or decomposes a decimal value to and from its individual +// parts. +// +// There are four separate parts: +// +// 1. boolean negative flag +// 2. form byte with three possible states (finite=0, infinite=1, NaN=2) +// 3. base-2 big-endian integer coefficient (also known as a significand) byte +// slice +// 4. int32 exponent +// +// These are composed into a final value where +// +// decimal = (neg) (form=finite) coefficient * 10^exponent +// +// A coefficient with a length of zero represents zero. +// // If the form is not finite the coefficient and scale should be ignored. -// The negative parameter may be set to true for any form, although implementations are not required -// to respect the negative parameter in the non-finite form. // -// Implementations may choose to signal a negative zero or negative NaN, but implementations -// that do not support these may also ignore the negative zero or negative NaN without error. -// If an implementation does not support Infinity it may be converted into a NaN without error. -// If a value is set that is larger then what is supported by an implementation is attempted to -// be set, an error must be returned. -// Implementations must return an error if a NaN or Infinity is attempted to be set while neither -// are supported. +// The negative parameter may be set to true for any form, although +// implementations are not required to respect the negative parameter in the +// non-finite form. +// +// Implementations may choose to signal a negative zero or negative NaN, but +// implementations that do not support these may also ignore the negative zero +// or negative NaN without error. +// +// If an implementation does not support Infinity it may be converted into a NaN +// without error. +// +// If a value is set that is larger then what is supported by an implementation +// is attempted to be set, an error must be returned. +// +// Implementations must return an error if a NaN or Infinity is attempted to be +// set while neither are supported. type decomposer interface { - // Decompose returns the internal decimal state into parts. - // If the provided buf has sufficient capacity, buf may be returned as the coefficient with - // the value set and length set as appropriate. - Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) + // Decompose decomposes the decimal into parts. + // + // If the provided buf has sufficient capacity, buf may be returned as the + // coefficient with the value set and length set as appropriate. + Decompose(buf []byte) (form byte, neg bool, coeff []byte, exp int32) - // Compose sets the internal decimal value from parts. If the value cannot be - // represented then an error should be returned. + // Compose composes the decimal value from its parts. + // + // If the value cannot be represented, then an error should be returned. + // // The coefficent should not be modified. Successive calls to compose with // the same arguments should result in the same decimal value. - Compose(form byte, negative bool, coefficient []byte, exponent int32) error + Compose(form byte, neg bool, coeff []byte, exp int32) error } -// Decompose returns the internal decimal state into parts. -// If the provided buf has sufficient capacity, buf may be returned as the coefficient with -// the value set and length set as appropriate. -func (z *Big) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) { - negative = z.Sign() < 0 +// Decompose decomposes x into individual parts. +// +// If the provided buf has sufficient capacity, buf may be returned as the +// coefficient with the value and length set as appropriate. +// +// It implements the hidden decimalDecompose in the database/sql/driver package. +func (x *Big) Decompose(buf []byte) (form byte, neg bool, coeff []byte, exp int32) { + neg = x.Sign() < 0 switch { - case z.IsInf(0): + case x.IsInf(0): form = 1 return - case z.IsNaN(0): + case x.IsNaN(0): form = 2 return } - if !z.IsFinite() { + if !x.IsFinite() { panic("expected number to be finite") } - if z.exp > math.MaxInt32 { + if x.exp > math.MaxInt32 { panic("exponent exceeds max size") } - exponent = int32(z.exp) + exp = int32(x.exp) - if z.isCompact() { + if x.isCompact() { if cap(buf) >= 8 { - coefficient = buf[:8] + coeff = buf[:8] } else { - coefficient = make([]byte, 8) + coeff = make([]byte, 8) } - binary.BigEndian.PutUint64(coefficient, z.compact) + binary.BigEndian.PutUint64(coeff, x.compact) } else { - coefficient = z.unscaled.Bytes() // This returns a big-endian slice. + coeff = x.unscaled.Bytes() // This returns a big-endian slice. } return } -// Compose sets the internal decimal value from parts. If the value cannot be -// represented then an error should be returned. -func (z *Big) Compose(form byte, negative bool, coefficient []byte, exponent int32) error { +// Compose sets z to the decimal value composed from individual parts. +// +// It implements the hidden decimalCompose interface in the database/sql package. +func (z *Big) Compose(form byte, neg bool, coeff []byte, exp int32) error { switch form { - default: - return fmt.Errorf("unknown form: %v", form) case 0: - // Finite form below. + z.unscaled.SetBytes(coeff) + z.SetBigMantScale(&z.unscaled, -int(exp)) + if neg { + z.Neg(z) + } + return nil case 1: - z.SetInf(negative) + z.SetInf(neg) return nil case 2: z.SetNaN(false) return nil + default: + return fmt.Errorf("unknown form: %v", form) } - bigc := &big.Int{} - bigc.SetBytes(coefficient) - z.SetBigMantScale(bigc, -int(exponent)) - if negative { - z.Neg(z) - } - return nil } diff --git a/format.go b/format.go deleted file mode 100644 index 8a34100..0000000 --- a/format.go +++ /dev/null @@ -1,347 +0,0 @@ -package decimal - -import ( - "bytes" - "fmt" - "io" - "math/big" - "strconv" -) - -// allZeros returns true if every character in b is '0'. -func allZeros(b []byte) bool { - for _, c := range b { - if c != '0' { - return false - } - } - return true -} - -var zero = []byte{'0'} - -// roundString rounds the plain numeric string (e.g., "1234") b. -func roundString(b []byte, mode RoundingMode, pos bool, prec int) []byte { - if prec >= len(b) { - return append(b, bytes.Repeat(zero, prec-len(b))...) - } - - // Trim zeros until prec. This is useful when we can round exactly by simply - // chopping zeros off the end of the number. - if allZeros(b[prec:]) { - return b[:prec] - } - - b = b[:prec+1] - i := prec - 1 - - // Blindly increment b[i] and handle possible carries later. - switch mode { - case AwayFromZero: - b[i]++ - case ToZero: - // OK - case ToPositiveInf: - if pos { - b[i]++ - } - case ToNegativeInf: - if !pos { - b[i]++ - } - case ToNearestEven: - if b[i+1] > '5' || b[i+1] == '5' && b[i]%2 != 0 { - b[i]++ - } - case ToNearestAway: - if b[i+1] >= '5' { - b[i]++ - } - case ToNearestTowardZero: - if b[i+1] > '5' { - b[i]++ - } - } - - if b[i] != '9'+1 { - return b[:prec] - } - - // We had to carry. - b[i] = '0' - - for i--; i >= 0; i-- { - if b[i] != '9' { - b[i]++ - break - } - b[i] = '0' - } - - // Carried all the way over to the first column, so slide the buffer down - // and instead of reallocating. - if b[0] == '0' { - copy(b[1:], b) - b[0] = '1' - // We might end up with an extra digit of precision. E.g., given the - // decimal 9.9 with a requested precision of 1, we'd convert 99 -> 10. - // Let the calling code handle that case. - prec++ - } - return b[:prec] -} - -// formatCompact formats the compact decimal, x, as an unsigned integer. -func formatCompact(x uint64) []byte { - var b [20]byte - return strconv.AppendUint(b[0:0], uint64(x), 10) -} - -// formatUnscaled formats the unscaled (non-compact) decimal, unscaled, as an -// unsigned integer. -func formatUnscaled(unscaled *big.Int) []byte { - // math/big.MarshalText never returns an error, only nil, so there's no need - // to check for an error. Use MarshalText instead of Append because it limits - // us to one allocation. - b, _ := unscaled.MarshalText() - if b[0] == '-' { - b = b[1:] - } - return b -} - -// noWidth indicates the width of a formatted number wasn't set. -const noWidth = -1 - -type format byte - -const ( - normal format = iota // either sci or plain, depending on x - plain // forced plain - sci // forced sci -) - -//go:generate stringer -type=format - -type formatter struct { - w interface { - io.Writer - io.ByteWriter - WriteString(string) (int, error) - } - sign byte // leading '+' or ' ' flag - prec int // total precision - width int // min width - n int64 // cumulative number of bytes written to w -} - -func (f *formatter) WriteByte(c byte) error { - f.n++ - return f.w.WriteByte(c) -} - -func (f *formatter) WriteString(s string) (int, error) { - m, err := f.w.WriteString(s) - f.n += int64(m) - return m, err -} - -func (f *formatter) Write(p []byte) (n int, err error) { - n, err = f.w.Write(p) - f.n += int64(n) - return n, err -} - -var sciE = [2]byte{GDA: 'E', Go: 'e'} - -func (f *formatter) format(x *Big, format format, e byte) { - if x == nil { - f.WriteString("") - return - } - - o := x.Context.OperatingMode - if x.isSpecial() { - switch o { - case GDA: - f.WriteString(x.form.String()) - if x.IsNaN(0) && x.compact != 0 { - f.WriteString(strconv.FormatUint(x.compact, 10)) - } - case Go: - if x.IsNaN(0) { - f.WriteString("NaN") - } else if x.IsInf(+1) { - f.WriteString("+Inf") - } else { - f.WriteString("-Inf") - } - } - return - } - - if x.isZero() && o == Go { - // Go mode prints zeros different than GDA. - if f.width == noWidth { - f.WriteByte('0') - } else { - f.WriteString("0.") - io.CopyN(f, zeroReader{}, int64(f.width)) - } - return - } - - neg := x.Signbit() - if neg { - f.WriteByte('-') - } else if f.sign != 0 { - f.WriteByte(f.sign) - } - - var ( - b []byte - exp int - ) - if f.prec > 0 { - if x.isCompact() { - b = formatCompact(x.compact) - } else { - b = formatUnscaled(&x.unscaled) - } - orig := len(b) - b = roundString(b, x.Context.RoundingMode, !neg, f.prec) - exp = int(x.exp) + orig - len(b) - } else if f.prec < 0 { - f.prec = -f.prec - exp = -f.prec - } else { - b = []byte{'0'} - } - - // "Next, the adjusted exponent is calculated; this is the exponent, plus - // the number of characters in the converted coefficient, less one. That - // is, exponent+(clength-1), where clength is the length of the coefficient - // in decimal digits. - adj := exp + (len(b) - 1) - if format != sci { - if exp <= 0 && (format == plain || adj >= -6) { - // "If the exponent is less than or equal to zero and the adjusted - // exponent is greater than or equal to -6 the number will be - // converted to a character form without using exponential notation." - // - // - http://speleotrove.com/decimal/daconvs.html#reftostr - f.formatPlain(b, exp) - return - } - - // No decimal places, write b and fill with zeros. - if format == plain && exp > 0 { - f.Write(b) - io.CopyN(f, zeroReader{}, int64(exp)) - return - } - } - f.formatSci(b, adj, e) -} - -// formatSci returns the scientific version of b. -func (f *formatter) formatSci(b []byte, adj int, e byte) { - f.WriteByte(b[0]) - - if len(b) > 1 { - f.WriteByte('.') - f.Write(b[1:]) - } - - // If negative, the call to strconv.Itoa will add the minus sign for us. - f.WriteByte(e) - if adj > 0 { - f.WriteByte('+') - } - f.WriteString(strconv.Itoa(adj)) -} - -// formatPlain returns the plain string version of b. -func (f *formatter) formatPlain(b []byte, exp int) { - const zeroRadix = "0." - - switch radix := len(b) + exp; { - // log10(b) == scale, so immediately before b: 0.123456 - case radix == 0: - f.WriteString(zeroRadix) - f.Write(b) - - // log10(b) > scale, so somewhere inside b: 123.456 - case radix > 0: - f.Write(b[:radix]) - if radix < len(b) { - f.WriteByte('.') - f.Write(b[radix:]) - } - - // log10(b) < scale, so before p "0s" and before b: 0.00000123456 - default: - f.WriteString(zeroRadix) - io.CopyN(f, zeroReader{}, -int64(radix)) - - end := len(b) - if f.prec < end { - end = f.prec - } - f.Write(b[:end]) - } -} - -// TODO(eric): can we merge zeroReader and spaceReader into a "singleReader" or -// something and still maintain the same performance? - -// zeroReader is an io.Reader that, when read from, only provides the character -// '0'. -type zeroReader struct{} - -// Read implements io.Reader. -func (z zeroReader) Read(p []byte) (n int, err error) { - // zeroLiterals is 16 '0' bytes. It's used to speed up zeroReader's Read - // method. - const zeroLiterals = "0000000000000000" - for n < len(p) { - m := copy(p[n:], zeroLiterals) - if m == 0 { - break - } - n += m - } - return n, nil -} - -// spaceReader is an io.Reader that, when read from, only provides the -// character ' '. -type spaceReader struct{} - -// Read implements io.Reader. -func (s spaceReader) Read(p []byte) (n int, err error) { - // spaceLiterals is 16 ' ' bytes. It's used to speed up spaceReader's Read - // method. - const spaceLiterals = " " - for n < len(p) { - m := copy(p[n:], spaceLiterals) - if m == 0 { - break - } - n += m - } - return n, nil -} - -// stateWrapper is a wrapper around an io.Writer to add WriteByte and -// WriteString methods. -type stateWrapper struct{ fmt.State } - -func (w stateWrapper) WriteByte(c byte) error { - _, err := w.Write([]byte{c}) - return err -} - -func (w stateWrapper) WriteString(s string) (int, error) { - return io.WriteString(w.State, s) -} diff --git a/format_string.go b/format_string.go deleted file mode 100644 index bb84925..0000000 --- a/format_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -type=format"; DO NOT EDIT. - -package decimal - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[normal-0] - _ = x[plain-1] - _ = x[sci-2] -} - -const _format_name = "normalplainsci" - -var _format_index = [...]uint8{0, 6, 11, 14} - -func (i format) String() string { - if i >= format(len(_format_index)-1) { - return "format(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _format_name[_format_index[i]:_format_index[i+1]] -} diff --git a/internal/arith/abs.go b/internal/arith/abs.go index d91adfa..e82f794 100644 --- a/internal/arith/abs.go +++ b/internal/arith/abs.go @@ -1,6 +1,9 @@ package arith -import "math/big" +import ( + "math/big" + "math/bits" +) // Abs returns the absolute value of x. func Abs(x int64) uint64 { @@ -26,7 +29,7 @@ func Cmp(x, y uint64) int { // CmpShift compares x and y*shift. func CmpShift(x, y, shift uint64) int { - y1, y0 := Mul64(y, shift) + y1, y0 := bits.Mul64(y, shift) if y1 != 0 { return -1 } diff --git a/internal/arith/arith.go b/internal/arith/arith.go index 4aa1fe8..9125fbe 100644 --- a/internal/arith/arith.go +++ b/internal/arith/arith.go @@ -64,7 +64,7 @@ func MulPow10(x uint64, n uint64) (uint64, bool) { // 0 * 10^n = 0. return 0, x == 0 } - hi, lo := Mul64(x, p) + hi, lo := bits.Mul64(x, p) return lo, hi == 0 } @@ -126,6 +126,21 @@ func norm(z []big.Word) []big.Word { return z[0:i] } +func mulWW(x, y big.Word) (z1, z0 big.Word) { + zz1, zz0 := bits.Mul(uint(x), uint(y)) + return big.Word(zz1), big.Word(zz0) +} + +func addWW(x, y, c big.Word) (z1, z0 big.Word) { + zz1, zz0 := bits.Add(uint(x), uint(y), uint(c)) + return big.Word(zz0), big.Word(zz1) +} + +func subWW(x, y, c big.Word) (z1, z0 big.Word) { + zz1, zz0 := bits.Sub(uint(x), uint(y), uint(c)) + return big.Word(zz0), big.Word(zz1) +} + func mulAddWW(z, x []big.Word, y, r big.Word) []big.Word { m := len(x) z = makeWord(z, m+1) diff --git a/internal/arith/arith_1_11.go b/internal/arith/arith_1_11.go deleted file mode 100644 index b06a723..0000000 --- a/internal/arith/arith_1_11.go +++ /dev/null @@ -1,75 +0,0 @@ -// +build !go1.12 - -// The following is copied from math/big/bits/bits.go, licensed under the -// BSD 3-clause license: https://github.com/golang/go/blob/master/LICENSE - -package arith - -import "math/big" - -func Add64(x, y uint64) (sum, carryOut uint64) { - yc := y + 0 /* carry */ - sum = x + yc - if sum < x || yc < y { - carryOut = 1 - } - return -} - -func Sub64(x, y uint64) (diff, borrowOut uint64) { - yb := y - diff = x - yb - if diff > x || yb < y { - borrowOut = 1 - } - return -} - -func Mul64(x, y uint64) (hi, lo uint64) { - const mask32 = 1<<32 - 1 - x0 := x & mask32 - x1 := x >> 32 - y0 := y & mask32 - y1 := y >> 32 - w0 := x0 * y0 - t := x1*y0 + w0>>32 - w1 := t & mask32 - w2 := t >> 32 - w1 += x0 * y1 - hi = x1*y1 + w2 + w1>>32 - lo = x * y - return -} - -func mulWW(x, y big.Word) (z1, z0 big.Word) { - x0 := x & _M2 - x1 := x >> _W2 - y0 := y & _M2 - y1 := y >> _W2 - w0 := x0 * y0 - t := x1*y0 + w0>>_W2 - w1 := t & _M2 - w2 := t >> _W2 - w1 += x0 * y1 - z1 = x1*y1 + w2 + w1>>_W2 - z0 = x * y - return -} - -func addWW(x, y, c big.Word) (z1, z0 big.Word) { - yc := y + c - z0 = x + yc - if z0 < x || yc < y { - z1 = 1 - } - return -} - -func subWW(x, y, c big.Word) (z1, z0 big.Word) { - yc := y + c - z0 = x - yc - if z0 > x || yc < y { - z1 = 1 - } - return -} diff --git a/internal/arith/arith_1_12.go b/internal/arith/arith_1_12.go deleted file mode 100644 index b247c0f..0000000 --- a/internal/arith/arith_1_12.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build go1.12 - -package arith - -import ( - "math/big" - "math/bits" -) - -func Add64(x, y uint64) (sum, carryOut uint64) { - return bits.Add64(x, y, 0) -} - -func Sub64(x, y uint64) (diff, borrowOut uint64) { - return bits.Sub64(x, y, 0) -} - -func Mul64(x, y uint64) (hi, lo uint64) { - return bits.Mul64(x, y) -} - -func mulWW(x, y big.Word) (z1, z0 big.Word) { - zz1, zz0 := bits.Mul(uint(x), uint(y)) - return big.Word(zz1), big.Word(zz0) -} - -func addWW(x, y, c big.Word) (z1, z0 big.Word) { - zz1, zz0 := bits.Add(uint(x), uint(y), uint(c)) - return big.Word(zz0), big.Word(zz1) -} - -func subWW(x, y, c big.Word) (z1, z0 big.Word) { - zz1, zz0 := bits.Sub(uint(x), uint(y), uint(c)) - return big.Word(zz0), big.Word(zz1) -} diff --git a/math/cosine.go b/math/cosine.go index d1c66ac..badca88 100644 --- a/math/cosine.go +++ b/math/cosine.go @@ -1,7 +1,7 @@ package math import ( - stdMath "math" + "math" "math/bits" "github.com/ericlagergren/decimal" @@ -32,13 +32,13 @@ func prepCosine(z, x *decimal.Big, ctx decimal.Context) (*decimal.Big, int, bool // Adjust so we have ceil(v/10) + ctx.Precision, but check for overflows. // 1+((v-1)/10) will be wildly incorrect for v == 0, but x/y = 0 iff // x = 0 and y != 0. In this case, -2pi <= x >= 2pi, so we're fine. - prec, c := arith.Add64(1+((uv-1)/10), uint64(ctx.Precision)) + prec, c := bits.Add64(1+((uv-1)/10), uint64(ctx.Precision), 0) if c != 0 || prec > maxInt { return nil, 0, false } pctx := decimal.Context{Precision: int(prec)} - if uv <= stdMath.MaxInt64/2 { + if uv <= math.MaxInt64/2 { tmp.SetMantScale(v, 0) } @@ -71,9 +71,9 @@ func prepCosine(z, x *decimal.Big, ctx decimal.Context) (*decimal.Big, int, bool // We only need to do the calculation if xf >= 0.0004. Anything below that // and we're <= 0. var halved int - if xf = stdMath.Abs(xf); xf >= 0.0004 { + if xf = math.Abs(xf); xf >= 0.0004 { // Originally: ceil(log(xf/0.0048828125) / ln2) - halved = int(stdMath.Ceil(1.4427*stdMath.Log(xf) + 11)) + halved = int(math.Ceil(1.4427*math.Log(xf) + 11)) // The general case is halved > 0, since we only get 0 if xf is very // close to 0.0004. if halved > 0 { diff --git a/math/tangent.go b/math/tangent.go index a72a748..dd83d22 100644 --- a/math/tangent.go +++ b/math/tangent.go @@ -1,6 +1,8 @@ package math import ( + "math/bits" + "github.com/ericlagergren/decimal" "github.com/ericlagergren/decimal/internal/arith" "github.com/ericlagergren/decimal/misc" @@ -33,7 +35,7 @@ func prepTan(z, x *decimal.Big, ctx decimal.Context) (*decimal.Big, bool) { // Adjust so we have ceil(v/10) + ctx.Precision, but check for overflows. // 1+((v-1)/10) will be widly incorrect for v == 0, but x/y = 0 iff // x = 0 and y != 0. In this case, -2pi <= x >= 2pi, so we're fine. - prec, c := arith.Add64(1+((uv-1)/10), uint64(ctx.Precision)) + prec, c := bits.Add64(1+((uv-1)/10), uint64(ctx.Precision), 0) if c != 0 || prec > maxInt { return nil, false } From e50e6902c0ef3a2f5c63acbde4f88e15506f656c Mon Sep 17 00:00:00 2001 From: Eric Lagergren Date: Mon, 14 Oct 2019 01:03:06 -0700 Subject: [PATCH 4/6] stash --- big.go | 251 ++++++++++++++++++++++++++-------------------------- big_test.go | 54 +++++------ 2 files changed, 155 insertions(+), 150 deletions(-) diff --git a/big.go b/big.go index aa64d8f..5a9e4e5 100644 --- a/big.go +++ b/big.go @@ -171,28 +171,8 @@ func (e ErrNaN) Error() string { return e.Msg } var _ error = ErrNaN{} -// Append appends x to buf using the specified format and returns the resulting -// slice. -// -// The following verbs are recognized: -// -// 'e': -d.dddd±edd -// 'E': -d.dddd±Edd -// 'f': -dddd.dd -// 'F': same as 'f' -// 'g': same as 'f' or 'e', depending on x -// 'G': same as 'F' or 'E', depending on x -// 'z': same as 'g', but with trailing zeros and trailing decimal point -// 'Z': same as 'G', but with trailing zeros and trailing decimal point -// -// Note that 'z' and 'Z' are analogous to the ``fmt'' package's verb-flag -// combinations %#g and %#G, respectively. Because they're non-standard, 'z' and -// 'Z' may be removed or have their meaning changed depending on changes made to -// the ``fmt'' package. -// -// For every format the precision is the number of digits following the radix, -// except in the case of 'g' and 'G' where the precision is the number of -// significant digits. +// Append appends to buf the string form of x, as generated by x.Text, and +// returns the extended buffer. func (x *Big) Append(buf []byte, fmt byte, prec int) []byte { if x == nil { return append(buf, ""...) @@ -226,13 +206,17 @@ func (x *Big) Append(buf []byte, fmt byte, prec int) []byte { dec := make([]byte, 0, x.Precision()) if x.isCompact() { - dec = strconv.AppendUint(dec[0:0], uint64(x.compact), 10) + dec = strconv.AppendUint(dec[:0], uint64(x.compact), 10) } else { - dec = x.unscaled.Append(dec[0:0], 10) + dec = x.unscaled.Append(dec[:0], 10) + } + + if prec < 0 { + prec = len(dec) } switch fmt { - case 'g', 'G', 'z', 'Z': + case 'g', 'G': // "Next, the adjusted exponent is calculated; this is the exponent, plus // the number of characters in the converted coefficient, less one. That // is, exponent+(clength-1), where clength is the length of the coefficient @@ -244,19 +228,13 @@ func (x *Big) Append(buf []byte, fmt byte, prec int) []byte { // // - http://speleotrove.com/decimal/daconvs.html#reftostr if x.exp <= 0 && adj >= -6 { - if x.exp == 0 { - prec = 0 - } else { - prec += x.exp - } - const z = 'z' & 'Z' - return fmtF(buf, fmt&z != z, x.exp, prec, dec) + return fmtG(buf, x.exp, prec, dec) } - return fmtE(buf, fmt+'e'-'g', adj, prec, dec) + return fmtE(buf, fmt+'e'-'g', adj, prec-1, dec) case 'e', 'E': return fmtE(buf, fmt, x.exp+(len(dec)-1), prec, dec) case 'f', 'F': - return fmtF(buf, false, x.exp, prec, dec) + return fmtF(buf, x.exp, prec, dec) default: if neg { buf = buf[:len(buf)-1] @@ -265,7 +243,13 @@ func (x *Big) Append(buf []byte, fmt byte, prec int) []byte { } } +func printf(s string, args ...interface{}) { + fmt.Printf(s, args...) +} + func fmtE(buf []byte, fmt byte, adj, prec int, dec []byte) []byte { + printf("E adj:%d, prec:%d, r:%d, n:%d, dec:%s\n", adj, prec, adj+len(dec), len(dec), dec) + buf = append(buf, dec[0]) if prec > 0 { buf = append(buf, '.') @@ -284,78 +268,65 @@ func fmtE(buf []byte, fmt byte, adj, prec int, dec []byte) []byte { return strconv.AppendUint(buf, uint64(adj), 10) // adj >= 0 } -func fmtF(buf []byte, strip bool, exp, prec int, dec []byte) []byte { - fmt.Printf("exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", - exp, prec, exp+len(dec), len(dec), dec) +func fmtF(buf []byte, exp, prec int, dec []byte) []byte { + fmt.Printf("F exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", exp, prec, exp+len(dec), len(dec), dec) - if exp >= 0 { + switch radix := len(dec) + exp; { + // ddddd. + case exp >= 0: buf = append(buf, dec...) - buf = appendZeros(buf, exp-len(dec)) - if prec > exp { + buf = appendZeros(buf, exp) + if prec > 0 { buf = append(buf, '.') - buf = appendZeros(buf, prec-exp) + buf = appendZeros(buf, prec) } - return buf - } - if radix := exp + len(dec); radix <= 0 { + // d.ddddd + case radix > 0: + buf = append(buf, dec[:radix]...) + buf = append(buf, '.') + buf = appendPad(buf, dec[radix:], prec) + // 0.000dd or 0.ddddd + default: buf = append(buf, "0."...) m := min(-radix, prec) buf = appendZeros(buf, m) - if m == 0 || m < prec { + if m < prec { buf = append(buf, dec...) - } - if n := -radix + len(dec); n < prec { - buf = appendZeros(buf, prec-n) - } - } else { - buf = append(buf, dec[:radix]...) - buf = append(buf, '.') - m := min(radix+prec, len(dec)) - fmt.Println("m=", m, "p=", radix+prec, "r=", radix, "p=", prec) - buf = append(buf, dec[radix:m]...) - if (m - radix) < prec { - buf = appendZeros(buf, prec-(m-radix)) + buf = appendZeros(buf, prec-(m+len(dec))) } } return buf +} + +func fmtG(buf []byte, exp, prec int, dec []byte) []byte { + fmt.Printf("G exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", exp, prec, exp+len(dec), len(dec), dec) + + if n := len(dec) - prec; n > 0 { + dec = dec[:prec] + exp += n + fmt.Println(n) + } + + fmt.Printf("G exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", exp, prec, exp+len(dec), len(dec), dec) - fmt.Println("r=", len(dec)+exp, "e=", exp, "p=", prec, strip, `"`+string(dec)+`"`) switch radix := len(dec) + exp; { - // 123[.456] + // ddddd. + case exp >= 0: + buf = append(buf, dec...) + // d.ddddd case radix > 0: buf = append(buf, dec[:radix]...) - println(prec != 0, !strip, !allzero(dec[radix:])) - if prec != 0 && (!strip || !allzero(dec[radix:])) { - buf = append(buf, '.') - buf = appendPad(buf, dec[radix:], prec) - } - // 0.00000123456 - case radix < 0: - buf = append(buf, '0') - if min := min(-radix, prec); !strip || min >= prec { - buf = append(buf, '.') - buf = appendZeros(buf, min) - if min < prec { - buf = appendPad(buf, dec, prec-min) - } - } - // 0[.0000...] or 0.123456 + buf = append(buf, '.') + buf = append(buf, dec[radix:]...) + // 0.000dd or 0.ddddd default: buf = append(buf, "0."...) - buf = appendPad(buf, dec, prec) + buf = appendZeros(buf, -radix) + buf = append(buf, dec...) } return buf } -func allzero(p []byte) bool { - for _, c := range p { - if c != '0' { - return false - } - } - return true -} - // appendPad appends x up to x[:prec] to buf, right-padding with '0's if // len(x) < prec. func appendPad(buf, x []byte, prec int) []byte { @@ -754,22 +725,12 @@ func (x *Big) Float(z *big.Float) *big.Float { return z } -// Format implements the fmt.Formatter interface. -// -// Format recognizes the same format verbs as Append, as well as the following -// aliases for %g: -// -// %s, %d, and %v -// -// Additionally, the verb %q is recognized as an alias of %s and is quoted -// appropriately. +// Format implements fmt.Formatter, recognizing the same format verbs as Append. // // When used in conjunction with any of the recognized verbs, Format honors all // flags in the manner described for floating point numbers in the ``fmt'' // package. // -// The default precision for %e, %f, and %g is the decimal's current precision. -// // All other unrecognized format and flag combinations (such as %#v) are passed // through to the ``fmt'' package. func (x *Big) Format(s fmt.State, c rune) { @@ -777,22 +738,18 @@ func (x *Big) Format(s fmt.State, c rune) { x.validate() } - var quote string // either `"` or "" + prec, hasPrec := s.Precision() + if !hasPrec { + prec = 6 + } + switch c { case 'e', 'E', 'f', 'F': // OK case 'g', 'G': - if s.Flag('#') { - c += 'e' - 'g' - } - case 'q': - quote = `"` - if s.Flag('#') { - quote = "`" + if !hasPrec { + prec = -1 } - c = 'g' - case 's', 'd': - c = 'g' case 'v': // Handle %#v as a special case. if !s.Flag('#') { @@ -813,12 +770,11 @@ func (x *Big) Format(s fmt.State, c rune) { return } - prec, ok := s.Precision() - if !ok { - prec = x.Precision() + cap := 10 + if prec > 0 { + cap += prec } - buf := make([]byte, 0, x.Precision()+1) - buf = x.Append(buf, byte(c), prec) + buf := x.Append(make([]byte, 0, cap), byte(c), prec) var sign string switch { @@ -838,6 +794,42 @@ func (x *Big) Format(s fmt.State, c rune) { sign = " " } + println(string(buf)) + // Sharp flag requires a decimal point. + if s.Flag('#') { + digits := prec + if digits < 0 { + digits = 6 + } + tail := make([]byte, 0, 6) + hasDot := false + for i := 0; i < len(buf); i++ { + switch buf[i] { + case '.': + hasDot = true + case 'e', 'E': + tail = append(tail, buf[i:]...) + buf = buf[:i] + default: + digits-- + } + } + if !hasDot { + buf = append(buf, '.') + } + for ; digits > 0; digits-- { + buf = append(buf, '0') + } + buf = append(buf, tail...) + } else if c == 'g' || c == 'G' { + for i := len(buf) - 1; i > 0; i-- { + if buf[i] != '0' && buf[i] != '.' { + break + } + buf = buf[:i] + } + } + var padding int if width, ok := s.Width(); ok && width > len(sign)+len(buf) { padding = width - len(sign) - len(buf) @@ -847,21 +839,15 @@ func (x *Big) Format(s fmt.State, c rune) { case s.Flag('0') && !x.IsInf(0): writeN(s, sign, 1) writeN(s, "0", padding) - writeN(s, quote, 1) s.Write(buf) - writeN(s, quote, 1) case s.Flag('-'): writeN(s, sign, 1) - writeN(s, quote, 1) s.Write(buf) - writeN(s, quote, 1) writeN(s, " ", padding) default: writeN(s, " ", padding) writeN(s, sign, 1) - writeN(s, quote, 1) s.Write(buf) - writeN(s, quote, 1) } } @@ -1079,12 +1065,12 @@ func (x *Big) IsInt() bool { return exp >= 0 } -// MarshalText implements encoding.TextMarshaler. +// MarshalText implements encoding.TextMarshaler, formatting x like x.String. func (x *Big) MarshalText() ([]byte, error) { if debug { x.validate() } - return x.Append(nil, 's', -1), nil + return x.Append(make([]byte, 0, 10), 'g', -1), nil } // Mul sets z to x * y and returns z. @@ -1572,16 +1558,33 @@ func (x *Big) Signbit() bool { return x.form&signbit != 0 } -// String returns the string representation of x. It's equivalent to the %s verb -// discussed in the Format method's documentation. Special cases depend on the -// OperatingMode. +// String formats x like x.Text('g', -1). func (x *Big) String() string { - return string(x.Append(nil, 's', -1)) + return string(x.Text('g', -1)) } // Sub sets z to x - y and returns z. func (z *Big) Sub(x, y *Big) *Big { return z.Context.Sub(z, x, y) } +// Text appends x to buf using the specified format and returns the resulting +// slice. +// +// The following verbs are recognized: +// +// 'e': -d.dddd±edd +// 'E': -d.dddd±Edd +// 'f': -dddd.dd +// 'F': same as 'f' +// 'g': same as 'f' or 'e', depending on x +// 'G': same as 'F' or 'E', depending on x +// +// For every format the precision is the number of digits following the radix, +// except in the case of 'g' and 'G' where the precision is the number of +// significant digits. +func (x *Big) Text(fmt byte, prec int) string { + return string(x.Append(make([]byte, 0, 10), fmt, prec)) +} + // UnmarshalJSON implements json.Unmarshaler. func (z *Big) UnmarshalJSON(data []byte) error { if len(data) >= 2 && data[0] == '"' && data[len(data)-1] == '"' { diff --git a/big_test.go b/big_test.go index 5e3b53a..1dad151 100644 --- a/big_test.go +++ b/big_test.go @@ -81,21 +81,26 @@ func TestBig_Float(t *testing.T) { // TestDecimal_Format tests Decimal.Format. The test cases are largely taken // from the fmt package's test cases. func TestDecimal_Format(t *testing.T) { + const ( + posInf = "+inf" + negInf = "-inf" + NaN = "NaN" + ) for i, s := range []struct { format string input string want string }{ - {"%s", ".12", "0.12"}, - {"%s", "12", "12"}, + {"%f", "1e-3", "0.001000"}, + {"%f", "1e+3", "1000.000000"}, + {"%.3f", "1", "1.000"}, {"%.5g", "1", "1"}, - {"%s", "12.34", "12.34"}, {"%.3g", "12.34", "12.3"}, {"'%5.2f'", "0.", "' 0.00'"}, {"%.10f", "0.1234567891", "0.1234567891"}, {"%.10f", "0.01", "0.0100000000"}, {"%.10f", "0.0000000000000000000000000000000000000000000000000000000000001", "0.0000000000"}, - {"%+.3e", "0.0", "+0.000e-01"}, // +00 -> -01 + {"%+.3e", "0.0", "+0.000e-01"}, // +0.000e+00 {"%+.3e", "1.0", "+1.000e+00"}, {"%+.3f", "-1.0", "-1.000"}, {"%+.3F", "-1.0", "-1.000"}, @@ -111,7 +116,7 @@ func TestDecimal_Format(t *testing.T) { {"%+10.2f", "-1.0", " -1.00"}, {"% .3E", "-1.0", "-1.000E+00"}, {"% .3e", "1.0", " 1.000e+00"}, - {"%+.3g", "0.0", "+0.0"}, // += .0 + {"%+.3g", "0.0", "+0"}, {"%+.3g", "1.0", "+1"}, {"%+.3g", "-1.0", "-1"}, {"% .3g", "-1.0", "-1"}, @@ -120,7 +125,7 @@ func TestDecimal_Format(t *testing.T) { {"%#g", "-1.0", "-1.00000"}, {"%#g", "1.1", "1.10000"}, {"%#g", "123456.0", "123456."}, - {"%#g", "1234567.0", "1.234567e+06"}, + {"%#g", "1234567.0", "1234567.0"}, // 1.234567e+06 {"%#g", "1230000.0", "1.23000e+06"}, {"%#g", "1000000.0", "1.00000e+06"}, {"%#.0f", "1.0", "1."}, @@ -139,29 +144,26 @@ func TestDecimal_Format(t *testing.T) { {"%#.4g", "123.0", "123.0"}, {"%#.4g", "123000.0", "1.230e+05"}, {"%#9.4g", "1.0", " 1.000"}, - {"%.68f", "1.0", zeroFill("1.", 68, "")}, - {"%.68f", "-1.0", zeroFill("-1.", 68, "")}, - // float infinites and NaNs - {"%f", "+Inf", "Infinity"}, - {"%.1f", "-Inf", "-Infinity"}, - {"% f", "NaN", " NaN"}, - {"%20f", "+Inf", " Infinity"}, - {"% 20F", "+Inf", " Infinity"}, - {"% 20e", "-Inf", " -Infinity"}, - {"%+20E", "-Inf", " -Infinity"}, - {"% +20g", "-Inf", " -Infinity"}, - {"%+-20G", "+Inf", "+Infinity "}, - {"%20e", "NaN", " NaN"}, - {"% +20E", "NaN", " +NaN"}, - {"% -20g", "NaN", " NaN "}, - {"%+-20G", "NaN", "+NaN "}, - // Zero padding does not apply to infinities and NaN. - {"%+020e", "+Inf", " +Infinity"}, - {"%-020f", "-Inf", "-Infinity "}, - {"%-020E", "NaN", "NaN "}, + {"%f", posInf, "+Inf"}, + {"%.1f", negInf, "-Inf"}, + {"% f", NaN, " NaN"}, + {"%20f", posInf, " +Inf"}, + {"% 20F", posInf, " Inf"}, + {"% 20e", negInf, " -Inf"}, + {"%+20E", negInf, " -Inf"}, + {"% +20g", negInf, " -Inf"}, + {"%+-20G", posInf, "+Inf "}, + {"%20e", NaN, " NaN"}, + {"% +20E", NaN, " +NaN"}, + {"% -20g", NaN, " NaN "}, + {"%+-20G", NaN, "+NaN "}, + {"%+020e", posInf, " +Inf"}, + {"%-020f", negInf, "-Inf "}, + {"%-020E", NaN, "NaN "}, } { //t.Run(strconv.Itoa(i), func(t *testing.T) { z, _ := new(decimal.Big).SetString(s.input) + fmt.Println("input=", s.input, -z.Scale()) got := fmt.Sprintf(s.format, z) if got != s.want { t.Fatalf(`#%d: printf("%s", "%s") From 96221e9514443790334a42f57baf6da714d79d1b Mon Sep 17 00:00:00 2001 From: Eric Lagergren Date: Sat, 18 Jan 2020 16:34:33 -0800 Subject: [PATCH 5/6] new format API Signed-off-by: Eric Lagergren --- .gitignore | 2 +- .travis.yml | 2 +- big.go | 225 +++++++++++++++++++++++++++++----------------------- big_test.go | 183 ++++++++++++++++++------------------------ 4 files changed, 207 insertions(+), 205 deletions(-) diff --git a/.gitignore b/.gitignore index 49cc08a..06e9a87 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,7 @@ tags # Internal literature/ -j/ +3p/ # Fuzzing fuzz/**/corpus diff --git a/.travis.yml b/.travis.yml index f256b43..8a836dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - "1.11.x" - "1.12.x" + - "1.13.x" - master cache: diff --git a/big.go b/big.go index 5a9e4e5..874eff8 100644 --- a/big.go +++ b/big.go @@ -211,30 +211,77 @@ func (x *Big) Append(buf []byte, fmt byte, prec int) []byte { dec = x.unscaled.Append(dec[:0], 10) } + // Normalize x such that 0 <= x < 1. + // + // actual decimal normalized exp+len + // ---------- -------------- --------------- --------- + // 123400 1234*10^2 0.1234*10^6 2+4=6 + // 12340 1234*10^1 0.1234*10^5 1+4=5 + // 1234 1234*10^0 0.1234*10^4 0+4=4 + // 1234.0 12340*10^-1 0.12340*10^4 -1+5=4 + // 1234.00 123400*10^-2 0.123400*10^4 -2+6=4 + // 123.4 1234*10^-1 0.1234*10^3 -1+4=3 + // 12.34 1234*10^-2 0.1234*10^2 -2+4=2 + // 1.234 1234*10^-3 0.1234*10^1 -3+4=1 + // 1.0 10*10-1 0.10*10^1 -1+2=1 + // 0.0 00*10^-1 0.00*10^1 -1+2=1 + // 0.1234 1234*10^-4 0.1234*10^0 -4+4=0 + // 0.001234 1234*10^-5 0.1234*10^-1 -5+4=-1 + // + norm := x.exp + len(dec) + if prec < 0 { - prec = len(dec) + dec = roundShortest(dec, norm) + switch fmt { + case 'e', 'E': + prec = len(dec) - 1 + case 'f', 'F': + prec = max(len(dec)-norm, 0) + case 'g', 'G': + prec = len(dec) + } + } else { + switch fmt { + case 'e', 'E': + dec = round(dec, 1+prec) + case 'f', 'F': + dec = round(dec, len(dec)+norm+prec) + case 'g', 'G': + if prec == 0 { + prec = 1 + } + dec = round(dec, prec) + } } switch fmt { case 'g', 'G': - // "Next, the adjusted exponent is calculated; this is the exponent, plus - // the number of characters in the converted coefficient, less one. That - // is, exponent+(clength-1), where clength is the length of the coefficient - // in decimal digits. - adj := x.exp + (len(dec) - 1) - // "If the exponent is less than or equal to zero and the adjusted - // exponent is greater than or equal to -6 the number will be converted - // to a character form without using exponential notation." + // Decide whether to use regular or exponential notation. + // + // Next, the adjusted exponent is calculated; this is the exponent, + // plus the number of characters in the converted coefficient, less + // one. That is, exponent+(clength-1), where clength is the length of + // the coefficient in decimal digits. + // + // If the exponent is less than or equal to zero and the adjusted + // exponent is greater than or equal to -6 the number will be converted + // to a character form without using exponential notation. // // - http://speleotrove.com/decimal/daconvs.html#reftostr - if x.exp <= 0 && adj >= -6 { - return fmtG(buf, x.exp, prec, dec) + if -6 <= norm-1 && x.exp <= 0 { + if prec > norm { + prec = len(dec) + } + return fmtF(buf, max(prec-norm, 0), dec, norm) + } + if prec > len(dec) { + prec = len(dec) } - return fmtE(buf, fmt+'e'-'g', adj, prec-1, dec) + return fmtE(buf, fmt+'e'-'g', prec-1, dec, norm) case 'e', 'E': - return fmtE(buf, fmt, x.exp+(len(dec)-1), prec, dec) + return fmtE(buf, fmt, prec, dec, norm) case 'f', 'F': - return fmtF(buf, x.exp, prec, dec) + return fmtF(buf, prec, dec, norm) default: if neg { buf = buf[:len(buf)-1] @@ -243,98 +290,88 @@ func (x *Big) Append(buf []byte, fmt byte, prec int) []byte { } } -func printf(s string, args ...interface{}) { - fmt.Printf(s, args...) +func max(a, b int) int { + if a > b { + return a + } + return b } -func fmtE(buf []byte, fmt byte, adj, prec int, dec []byte) []byte { - printf("E adj:%d, prec:%d, r:%d, n:%d, dec:%s\n", adj, prec, adj+len(dec), len(dec), dec) - - buf = append(buf, dec[0]) - if prec > 0 { - buf = append(buf, '.') - buf = appendPad(buf, dec[1:], prec) +func round(dec []byte, n int) []byte { + // TODO(eric): actually round + if n < 0 || n >= len(dec) { + return dec } - buf = append(buf, fmt) - if adj >= 0 { - buf = append(buf, '+') - } else { - buf = append(buf, '-') - adj = -adj + return dec[:n] +} + +func roundShortest(dec []byte, exp int) []byte { + // exp < 0 : 0.[000]ddd + // exp >= len(dec): ddddd + if exp < 0 || exp >= len(dec) { + return dec } - if adj < 10 { - buf = append(buf, '0') // 01, 02, ..., 09 + i := len(dec) - 1 + for i >= len(dec)-exp { + if dec[i] != '0' { + break + } + i-- } - return strconv.AppendUint(buf, uint64(adj), 10) // adj >= 0 + return dec[:i+1] } -func fmtF(buf []byte, exp, prec int, dec []byte) []byte { - fmt.Printf("F exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", exp, prec, exp+len(dec), len(dec), dec) - - switch radix := len(dec) + exp; { - // ddddd. - case exp >= 0: - buf = append(buf, dec...) - buf = appendZeros(buf, exp) - if prec > 0 { - buf = append(buf, '.') - buf = appendZeros(buf, prec) - } - // d.ddddd - case radix > 0: - buf = append(buf, dec[:radix]...) +func fmtE(buf []byte, fmt byte, prec int, dec []byte, exp int) []byte { + if len(dec) > 0 { + buf = append(buf, dec[0]) + } else { + buf = append(buf, '0') + } + if prec > 0 { buf = append(buf, '.') - buf = appendPad(buf, dec[radix:], prec) - // 0.000dd or 0.ddddd - default: - buf = append(buf, "0."...) - m := min(-radix, prec) - buf = appendZeros(buf, m) - if m < prec { - buf = append(buf, dec...) - buf = appendZeros(buf, prec-(m+len(dec))) + i := 1 + m := min(len(dec), prec+1) + if i < m { + buf = append(buf, dec[i:m]...) + i = m } + buf = appendZeros(buf, prec-i+1) // i <= prec } - return buf -} - -func fmtG(buf []byte, exp, prec int, dec []byte) []byte { - fmt.Printf("G exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", exp, prec, exp+len(dec), len(dec), dec) - if n := len(dec) - prec; n > 0 { - dec = dec[:prec] - exp += n - fmt.Println(n) + if len(dec) > 0 { + exp-- } - - fmt.Printf("G exp:%d, prec:%d, r:%d, n:%d, dec:%s\n", exp, prec, exp+len(dec), len(dec), dec) - - switch radix := len(dec) + exp; { - // ddddd. - case exp >= 0: - buf = append(buf, dec...) - // d.ddddd - case radix > 0: - buf = append(buf, dec[:radix]...) - buf = append(buf, '.') - buf = append(buf, dec[radix:]...) - // 0.000dd or 0.ddddd - default: - buf = append(buf, "0."...) - buf = appendZeros(buf, -radix) - buf = append(buf, dec...) + buf = append(buf, fmt) + if exp < 0 { + buf = append(buf, '-') + exp = -exp + } else { + buf = append(buf, '+') } - return buf + if exp < 10 { + buf = append(buf, '0') // 01, 02, ..., 09 + } + return strconv.AppendUint(buf, uint64(exp), 10) // exp >= 0 } -// appendPad appends x up to x[:prec] to buf, right-padding with '0's if -// len(x) < prec. -func appendPad(buf, x []byte, prec int) []byte { - if n := prec - len(x); n > 0 { - buf = append(buf, x...) - buf = appendZeros(buf, n) +func fmtF(buf []byte, prec int, dec []byte, exp int) []byte { + if exp > 0 { + m := min(len(dec), exp) + buf = append(buf, dec[:m]...) + buf = appendZeros(buf, exp-m) } else { - buf = append(buf, x[:prec]...) + buf = append(buf, '0') + } + + if prec > 0 { + buf = append(buf, '.') + for i := 0; i < prec; i++ { + c := byte('0') + if j := i + exp; 0 <= j && j < len(dec) { + c = dec[j] + } + buf = append(buf, c) + } } return buf } @@ -794,7 +831,6 @@ func (x *Big) Format(s fmt.State, c rune) { sign = " " } - println(string(buf)) // Sharp flag requires a decimal point. if s.Flag('#') { digits := prec @@ -821,13 +857,6 @@ func (x *Big) Format(s fmt.State, c rune) { buf = append(buf, '0') } buf = append(buf, tail...) - } else if c == 'g' || c == 'G' { - for i := len(buf) - 1; i > 0; i-- { - if buf[i] != '0' && buf[i] != '.' { - break - } - buf = buf[:i] - } } var padding int diff --git a/big_test.go b/big_test.go index 1dad151..ce3c575 100644 --- a/big_test.go +++ b/big_test.go @@ -28,7 +28,7 @@ func TestBig_Rat(t *testing.T) { test.CTR.Test(t) } func TestBig_Reduce(t *testing.T) { test.Reduce.Test(t) } func TestBig_Rem(t *testing.T) { test.Rem.Test(t) } func TestBig_RoundToInt(t *testing.T) { test.RoundToInt.Test(t) } -func TestBig_SetString(t *testing.T) { test.CTS.Test(t) /* Same as CFS */ } +func TestBig_SetString(t *testing.T) { test.CFS.Test(t) } func TestBig_Sign(t *testing.T) { test.Sign.Test(t) } func TestBig_SignBit(t *testing.T) { test.Signbit.Test(t) } func TestBig_String(t *testing.T) { test.CTS.Test(t) } @@ -57,7 +57,8 @@ func randDec() string { return string(b) } -var randDecs = func() (a [5000]string) { +var randDecs = func() []string { + a := make([]string, 5000) for i := range a { a[i] = randDec() } @@ -78,92 +79,93 @@ func TestBig_Float(t *testing.T) { } } -// TestDecimal_Format tests Decimal.Format. The test cases are largely taken -// from the fmt package's test cases. -func TestDecimal_Format(t *testing.T) { +// TestBig_Format tests Big.Format +// +// The test cases are largely taken from the fmt package's test cases. +func TestBig_Format(t *testing.T) { const ( posInf = "+inf" negInf = "-inf" NaN = "NaN" ) + // Comments after a test case indicate the original fmt package test case. for i, s := range []struct { format string input string want string }{ - {"%f", "1e-3", "0.001000"}, - {"%f", "1e+3", "1000.000000"}, - {"%.3f", "1", "1.000"}, - {"%.5g", "1", "1"}, - {"%.3g", "12.34", "12.3"}, - {"'%5.2f'", "0.", "' 0.00'"}, - {"%.10f", "0.1234567891", "0.1234567891"}, - {"%.10f", "0.01", "0.0100000000"}, - {"%.10f", "0.0000000000000000000000000000000000000000000000000000000000001", "0.0000000000"}, - {"%+.3e", "0.0", "+0.000e-01"}, // +0.000e+00 - {"%+.3e", "1.0", "+1.000e+00"}, - {"%+.3f", "-1.0", "-1.000"}, - {"%+.3F", "-1.0", "-1.000"}, - {"%+07.2f", "1.0", "+001.00"}, - {"%+07.2f", "-1.0", "-001.00"}, - {"%-07.2f", "1.0", "1.00 "}, - {"%-07.2f", "-1.0", "-1.00 "}, - {"%+-07.2f", "1.0", "+1.00 "}, - {"%+-07.2f", "-1.0", "-1.00 "}, - {"%-+07.2f", "1.0", "+1.00 "}, - {"%-+07.2f", "-1.0", "-1.00 "}, - {"%+10.2f", "+1.0", " +1.00"}, - {"%+10.2f", "-1.0", " -1.00"}, - {"% .3E", "-1.0", "-1.000E+00"}, - {"% .3e", "1.0", " 1.000e+00"}, - {"%+.3g", "0.0", "+0"}, - {"%+.3g", "1.0", "+1"}, - {"%+.3g", "-1.0", "-1"}, - {"% .3g", "-1.0", "-1"}, - {"% .3g", "1.0", " 1"}, - {"%#g", "1e-323", "1.00000e-323"}, - {"%#g", "-1.0", "-1.00000"}, - {"%#g", "1.1", "1.10000"}, - {"%#g", "123456.0", "123456."}, - {"%#g", "1234567.0", "1234567.0"}, // 1.234567e+06 - {"%#g", "1230000.0", "1.23000e+06"}, - {"%#g", "1000000.0", "1.00000e+06"}, - {"%#.0f", "1.0", "1."}, - {"%#.0e", "1.0", "1.e+00"}, - {"%#.0g", "1.0", "1."}, - {"%#.0g", "1100000.0", "1.e+06"}, - {"%#.4f", "1.0", "1.0000"}, - {"%#.4e", "1.0", "1.0000e+00"}, - {"%#.4g", "1.0", "1.000"}, - {"%#.4g", "100000.0", "1.000e+05"}, - {"%#.0f", "123.0", "123."}, - {"%#.0e", "123.0", "1.e+02"}, - {"%#.0g", "123.0", "1.e+02"}, - {"%#.4f", "123.0", "123.0000"}, - {"%#.4e", "123.0", "1.2300e+02"}, - {"%#.4g", "123.0", "123.0"}, - {"%#.4g", "123000.0", "1.230e+05"}, - {"%#9.4g", "1.0", " 1.000"}, - {"%f", posInf, "+Inf"}, - {"%.1f", negInf, "-Inf"}, - {"% f", NaN, " NaN"}, - {"%20f", posInf, " +Inf"}, - {"% 20F", posInf, " Inf"}, - {"% 20e", negInf, " -Inf"}, - {"%+20E", negInf, " -Inf"}, - {"% +20g", negInf, " -Inf"}, - {"%+-20G", posInf, "+Inf "}, - {"%20e", NaN, " NaN"}, - {"% +20E", NaN, " +NaN"}, - {"% -20g", NaN, " NaN "}, - {"%+-20G", NaN, "+NaN "}, - {"%+020e", posInf, " +Inf"}, - {"%-020f", negInf, "-Inf "}, - {"%-020E", NaN, "NaN "}, + 0: {"%f", "1234", "1234.000000"}, + 1: {"%f", "1e-3", "0.001000"}, + 2: {"%f", "1e+3", "1000.000000"}, + 3: {"%.3f", "1", "1.000"}, + 4: {"%.5g", "1", "1"}, + 5: {"%.3g", "12.34", "12.3"}, + 6: {"'%5.2f'", "0.", "' 0.00'"}, + 7: {"%.10f", "0.1234567891", "0.1234567891"}, + 8: {"%.10f", "0.01", "0.0100000000"}, + 9: {"%.10f", "0.0000000000000000000000000000000000000000000000000000000000001", "0.0000000000"}, + 10: {"%+.3e", "0.0", "+0.000e-01"}, // +0.000e+00 + 11: {"%+.3e", "1.0", "+1.000e+00"}, + 12: {"%+.3f", "-1.0", "-1.000"}, + 13: {"%+.3F", "-1.0", "-1.000"}, + 14: {"%+07.2f", "1.0", "+001.00"}, + 15: {"%+07.2f", "-1.0", "-001.00"}, + 16: {"%-07.2f", "1.0", "1.00 "}, + 17: {"%-07.2f", "-1.0", "-1.00 "}, + 18: {"%+-07.2f", "1.0", "+1.00 "}, + 19: {"%+-07.2f", "-1.0", "-1.00 "}, + 20: {"%-+07.2f", "1.0", "+1.00 "}, + 21: {"%-+07.2f", "-1.0", "-1.00 "}, + 22: {"%+10.2f", "+1.0", " +1.00"}, + 23: {"%+10.2f", "-1.0", " -1.00"}, + 24: {"% .3E", "-1.0", "-1.000E+00"}, + 25: {"% .3e", "1.0", " 1.000e+00"}, + 26: {"%+.3g", "0.0", "+0.0"}, // +0 + 27: {"%+.3g", "1.0", "+1.0"}, // +1 + 28: {"%+.3g", "-1.0", "-1.0"}, // -1 + 29: {"% .3g", "-1.0", "-1.0"}, // -1 + 30: {"% .3g", "1.0", " 1.0"}, // 1 + 31: {"%#g", "1e-323", "1.00000e-323"}, + 32: {"%#g", "-1.0", "-1.00000"}, + 33: {"%#g", "1.1", "1.10000"}, + 34: {"%#g", "123456.0", "123456."}, + 35: {"%#g", "1234567.0", "1234567."}, // 1.234567e+06 + 36: {"%#g", "1230000.0", "1230000."}, // 1.23000e+06 + 37: {"%#g", "1000000.0", "1000000."}, // 1000000.0 + 38: {"%#.0f", "1.0", "1."}, + 39: {"%#.0e", "1.0", "1.e+00"}, + 40: {"%#.0g", "1.0", "1."}, + 41: {"%#.0g", "1100000.0", "1000000."}, // 1.e+06 + 42: {"%#.4f", "1.0", "1.0000"}, + 43: {"%#.4e", "1.0", "1.0000e+00"}, + 44: {"%#.4g", "1.0", "1.000"}, + 45: {"%#.4g", "100000.0", "100000."}, // 1.000e+05 + 46: {"%#.0f", "123.0", "123."}, + 47: {"%#.0e", "123.0", "1.e+02"}, + 48: {"%#.0g", "123.0", "100."}, // 1.e+02 + 49: {"%#.4f", "123.0", "123.0000"}, + 50: {"%#.4e", "123.0", "1.2300e+02"}, + 51: {"%#.4g", "123.0", "123.0"}, + 52: {"%#.4g", "123000.0", "123000."}, // 1.230e+05 + 53: {"%#9.4g", "1.0", " 1.000"}, + 54: {"%f", posInf, "Infinity"}, // +Inf + 55: {"%.1f", negInf, "-Infinity"}, + 56: {"% f", NaN, " NaN"}, + 57: {"%20f", posInf, " Infinity"}, + 58: {"% 20F", posInf, " Infinity"}, + 59: {"% 20e", negInf, " -Infinity"}, + 60: {"%+20E", negInf, " -Infinity"}, + 61: {"% +20g", negInf, " -Infinity"}, + 62: {"%+-20G", posInf, "+Infinity "}, + 63: {"%20e", NaN, " NaN"}, + 64: {"% +20E", NaN, " +NaN"}, + 65: {"% -20g", NaN, " NaN "}, + 66: {"%+-20G", NaN, "+NaN "}, + 67: {"%+020e", posInf, " +Infinity"}, + 68: {"%-020f", negInf, "-Infinity "}, + 69: {"%-020E", NaN, "NaN "}, } { - //t.Run(strconv.Itoa(i), func(t *testing.T) { z, _ := new(decimal.Big).SetString(s.input) - fmt.Println("input=", s.input, -z.Scale()) got := fmt.Sprintf(s.format, z) if got != s.want { t.Fatalf(`#%d: printf("%s", "%s") @@ -171,16 +173,9 @@ got : %q wanted: %q `, i, s.format, s.input, got, s.want) } - //}) } } -// zeroFill generates zero-filled strings of the specified width. The length -// of the suffix (but not the prefix) is compensated for in the width calculation. -func zeroFill(prefix string, width int, suffix string) string { - return prefix + strings.Repeat("0", width-len(suffix)) + suffix -} - func TestBig_Int(t *testing.T) { for i, test := range randDecs { a, ok := new(decimal.Big).SetString(test) @@ -275,28 +270,6 @@ func TestBig_IsInt(t *testing.T) { } } -// func TestBig_Format(t *testing.T) { -// tests := [...]struct { -// format string -// a string -// b string -// }{ -// 0: {format: "%e", a: "1.234", b: "1.234"}, -// 1: {format: "%s", a: "1.2134124124", b: "1.2134124124"}, -// 2: {format: "%e", a: "1.00003e-12", b: "1.00003e-12"}, -// 3: {format: "%E", a: "1.000003E-12", b: "1.000003E-12"}, -// } -// for i, v := range tests { -// x, ok := new(decimal.Big).SetString(v.a) -// if !ok { -// t.Fatal("invalid SetString") -// } -// if fs := fmt.Sprintf(v.format, x); fs != v.b { -// t.Fatalf("#%d: wanted %q, got %q:", i, v.b, fs) -// } -// } -// } - func TestParallel(t *testing.T) { x := decimal.New(4, 0) y := decimal.New(3, 0) From 63aa8ab744191bf01a085f190bbbf691b764055c Mon Sep 17 00:00:00 2001 From: Eric Lagergren Date: Mon, 20 Jul 2020 01:41:59 -0700 Subject: [PATCH 6/6] test fixes Signed-off-by: Eric Lagergren --- big_test.go | 2 +- decomposer_test.go | 6 +++--- example_decimal_test.go | 30 +++++++++++++----------------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/big_test.go b/big_test.go index 8dbbcd1..f0e895e 100644 --- a/big_test.go +++ b/big_test.go @@ -346,7 +346,7 @@ got : %g func isSpecial(f float64) bool { return math.IsInf(f, 0) || math.IsNaN(f) } -func TestBig_Format(t *testing.T) { +func TestBig_Sprintf(t *testing.T) { x, _ := new(decimal.Big).SetString("200.0") x.Reduce() diff --git a/decomposer_test.go b/decomposer_test.go index 99e6e8a..f7b4beb 100644 --- a/decomposer_test.go +++ b/decomposer_test.go @@ -64,8 +64,8 @@ func TestDecomposerCompose(t *testing.T) { {N: "Zero", S: "0", Coef: nil, Exp: 0}, {N: "Normal-1", S: "123.456", Coef: []byte{0x01, 0xE2, 0x40}, Exp: -3}, {N: "Neg-1", S: "-123.456", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: -3}, - {N: "PosExp-1", S: "123456000", Coef: []byte{0x01, 0xE2, 0x40}, Exp: 3}, - {N: "PosExp-2", S: "-123456000", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: 3}, + {N: "PosExp-1", S: "1.23456e+08", Coef: []byte{0x01, 0xE2, 0x40}, Exp: 3}, + {N: "PosExp-2", S: "-1.23456e+08", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: 3}, {N: "AllDec-1", S: "0.123456", Coef: []byte{0x01, 0xE2, 0x40}, Exp: -6}, {N: "AllDec-2", S: "-0.123456", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: -6}, {N: "NaN-1", S: "NaN", Form: 2}, @@ -90,7 +90,7 @@ func TestDecomposerCompose(t *testing.T) { } return } - if s := fmt.Sprintf("%f", d); s != item.S { + if s := fmt.Sprintf("%g", d); s != item.S { t.Fatalf("unexpected value, got %q want %q", s, item.S) } }) diff --git a/example_decimal_test.go b/example_decimal_test.go index 6743447..7e2c890 100644 --- a/example_decimal_test.go +++ b/example_decimal_test.go @@ -12,15 +12,11 @@ func ExampleBig_Format() { fmt.Printf(format+"\n", x) } - print("%s", "12.34") - print("%q", "12.34") print("%.3g", "12.34") print("%.1f", "12.34") print("`%6.4g`", "500.44") print("'%-10.f'", "-404.040") // Output: - // 12.34 - // "12.34" // 12.3 // 12.3 // ` 500.4` @@ -34,15 +30,15 @@ func ExampleBig_Precision() { d := decimal.New(3, 5) fmt.Printf(` -%s has a precision of %d -%s has a precision of %d -%s has a precision of %d -%s has a precision of %d +%g has a precision of %d +%g has a precision of %d +%g has a precision of %d +%g has a precision of %d `, a, a.Precision(), b, b.Precision(), c, c.Precision(), d, d.Precision()) // Output: // // 12 has a precision of 2 - // 4.2E+3 has a precision of 2 + // 4.2e+03 has a precision of 2 // 12.345 has a precision of 5 // 0.00003 has a precision of 1 } @@ -58,7 +54,7 @@ func ExampleBig_Round() { fmt.Println(c.Round(5)) fmt.Println(d.Round(1)) // Output: - // 1.2E+3 + // 1.2e+03 // 54 // 60 // 0.002 @@ -70,13 +66,13 @@ func ExampleBig_Quantize() { c, _ := decimal.WithContext(decimal.Context128).SetString("-0.1") d, _ := decimal.WithContext(decimal.Context{OperatingMode: decimal.GDA}).SetString("-0") - fmt.Printf("A: %s\n", a.Quantize(3)) // 3 digits after radix - fmt.Printf("B: %s\n", b.Quantize(-2)) - fmt.Printf("C: %s\n", c.Quantize(1)) - fmt.Printf("D: %s\n", d.Quantize(-5)) + fmt.Printf("A: %g\n", a.Quantize(3)) + fmt.Printf("B: %g\n", b.Quantize(-2)) + fmt.Printf("C: %g\n", c.Quantize(1)) + fmt.Printf("D: %g\n", d.Quantize(-5)) // Output: - // A: 2.170 - // B: 2E+2 + // A: 2.17 + // B: 2e+02 // C: -0.1 - // D: -0E+5 + // D: -0e+05 }