| // Copyright 2017 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:generate stringer -type RoundingMode | |
| package number | |
| import ( | |
| "math" | |
| "strconv" | |
| ) | |
| // RoundingMode determines how a number is rounded to the desired precision. | |
| type RoundingMode byte | |
| const ( | |
| ToNearestEven RoundingMode = iota // towards the nearest integer, or towards an even number if equidistant. | |
| ToNearestZero // towards the nearest integer, or towards zero if equidistant. | |
| ToNearestAway // towards the nearest integer, or away from zero if equidistant. | |
| ToPositiveInf // towards infinity | |
| ToNegativeInf // towards negative infinity | |
| ToZero // towards zero | |
| AwayFromZero // away from zero | |
| numModes | |
| ) | |
| const maxIntDigits = 20 | |
| // A Decimal represents a floating point number in decimal format. | |
| // Digits represents a number [0, 1.0), and the absolute value represented by | |
| // Decimal is Digits * 10^Exp. Leading and trailing zeros may be omitted and Exp | |
| // may point outside a valid position in Digits. | |
| // | |
| // Examples: | |
| // | |
| // Number Decimal | |
| // 12345 Digits: [1, 2, 3, 4, 5], Exp: 5 | |
| // 12.345 Digits: [1, 2, 3, 4, 5], Exp: 2 | |
| // 12000 Digits: [1, 2], Exp: 5 | |
| // 12000.00 Digits: [1, 2], Exp: 5 | |
| // 0.00123 Digits: [1, 2, 3], Exp: -2 | |
| // 0 Digits: [], Exp: 0 | |
| type Decimal struct { | |
| digits | |
| buf [maxIntDigits]byte | |
| } | |
| type digits struct { | |
| Digits []byte // mantissa digits, big-endian | |
| Exp int32 // exponent | |
| Neg bool | |
| Inf bool // Takes precedence over Digits and Exp. | |
| NaN bool // Takes precedence over Inf. | |
| } | |
| // Digits represents a floating point number represented in digits of the | |
| // base in which a number is to be displayed. It is similar to Decimal, but | |
| // keeps track of trailing fraction zeros and the comma placement for | |
| // engineering notation. Digits must have at least one digit. | |
| // | |
| // Examples: | |
| // | |
| // Number Decimal | |
| // decimal | |
| // 12345 Digits: [1, 2, 3, 4, 5], Exp: 5 End: 5 | |
| // 12.345 Digits: [1, 2, 3, 4, 5], Exp: 2 End: 5 | |
| // 12000 Digits: [1, 2], Exp: 5 End: 5 | |
| // 12000.00 Digits: [1, 2], Exp: 5 End: 7 | |
| // 0.00123 Digits: [1, 2, 3], Exp: -2 End: 3 | |
| // 0 Digits: [], Exp: 0 End: 1 | |
| // scientific (actual exp is Exp - Comma) | |
| // 0e0 Digits: [0], Exp: 1, End: 1, Comma: 1 | |
| // .0e0 Digits: [0], Exp: 0, End: 1, Comma: 0 | |
| // 0.0e0 Digits: [0], Exp: 1, End: 2, Comma: 1 | |
| // 1.23e4 Digits: [1, 2, 3], Exp: 5, End: 3, Comma: 1 | |
| // .123e5 Digits: [1, 2, 3], Exp: 5, End: 3, Comma: 0 | |
| // engineering | |
| // 12.3e3 Digits: [1, 2, 3], Exp: 5, End: 3, Comma: 2 | |
| type Digits struct { | |
| digits | |
| // End indicates the end position of the number. | |
| End int32 // For decimals Exp <= End. For scientific len(Digits) <= End. | |
| // Comma is used for the comma position for scientific (always 0 or 1) and | |
| // engineering notation (always 0, 1, 2, or 3). | |
| Comma uint8 | |
| // IsScientific indicates whether this number is to be rendered as a | |
| // scientific number. | |
| IsScientific bool | |
| } | |
| func (d *Digits) NumFracDigits() int { | |
| if d.Exp >= d.End { | |
| return 0 | |
| } | |
| return int(d.End - d.Exp) | |
| } | |
| // normalize returns a new Decimal with leading and trailing zeros removed. | |
| func (d *Decimal) normalize() (n Decimal) { | |
| n = *d | |
| b := n.Digits | |
| // Strip leading zeros. Resulting number of digits is significant digits. | |
| for len(b) > 0 && b[0] == 0 { | |
| b = b[1:] | |
| n.Exp-- | |
| } | |
| // Strip trailing zeros | |
| for len(b) > 0 && b[len(b)-1] == 0 { | |
| b = b[:len(b)-1] | |
| } | |
| if len(b) == 0 { | |
| n.Exp = 0 | |
| } | |
| n.Digits = b | |
| return n | |
| } | |
| func (d *Decimal) clear() { | |
| b := d.Digits | |
| if b == nil { | |
| b = d.buf[:0] | |
| } | |
| *d = Decimal{} | |
| d.Digits = b[:0] | |
| } | |
| func (x *Decimal) String() string { | |
| if x.NaN { | |
| return "NaN" | |
| } | |
| var buf []byte | |
| if x.Neg { | |
| buf = append(buf, '-') | |
| } | |
| if x.Inf { | |
| buf = append(buf, "Inf"...) | |
| return string(buf) | |
| } | |
| switch { | |
| case len(x.Digits) == 0: | |
| buf = append(buf, '0') | |
| case x.Exp <= 0: | |
| // 0.00ddd | |
| buf = append(buf, "0."...) | |
| buf = appendZeros(buf, -int(x.Exp)) | |
| buf = appendDigits(buf, x.Digits) | |
| case /* 0 < */ int(x.Exp) < len(x.Digits): | |
| // dd.ddd | |
| buf = appendDigits(buf, x.Digits[:x.Exp]) | |
| buf = append(buf, '.') | |
| buf = appendDigits(buf, x.Digits[x.Exp:]) | |
| default: // len(x.Digits) <= x.Exp | |
| // ddd00 | |
| buf = appendDigits(buf, x.Digits) | |
| buf = appendZeros(buf, int(x.Exp)-len(x.Digits)) | |
| } | |
| return string(buf) | |
| } | |
| func appendDigits(buf []byte, digits []byte) []byte { | |
| for _, c := range digits { | |
| buf = append(buf, c+'0') | |
| } | |
| return buf | |
| } | |
| // appendZeros appends n 0 digits to buf and returns buf. | |
| func appendZeros(buf []byte, n int) []byte { | |
| for ; n > 0; n-- { | |
| buf = append(buf, '0') | |
| } | |
| return buf | |
| } | |
| func (d *digits) round(mode RoundingMode, n int) { | |
| if n >= len(d.Digits) { | |
| return | |
| } | |
| // Make rounding decision: The result mantissa is truncated ("rounded down") | |
| // by default. Decide if we need to increment, or "round up", the (unsigned) | |
| // mantissa. | |
| inc := false | |
| switch mode { | |
| case ToNegativeInf: | |
| inc = d.Neg | |
| case ToPositiveInf: | |
| inc = !d.Neg | |
| case ToZero: | |
| // nothing to do | |
| case AwayFromZero: | |
| inc = true | |
| case ToNearestEven: | |
| inc = d.Digits[n] > 5 || d.Digits[n] == 5 && | |
| (len(d.Digits) > n+1 || n == 0 || d.Digits[n-1]&1 != 0) | |
| case ToNearestAway: | |
| inc = d.Digits[n] >= 5 | |
| case ToNearestZero: | |
| inc = d.Digits[n] > 5 || d.Digits[n] == 5 && len(d.Digits) > n+1 | |
| default: | |
| panic("unreachable") | |
| } | |
| if inc { | |
| d.roundUp(n) | |
| } else { | |
| d.roundDown(n) | |
| } | |
| } | |
| // roundFloat rounds a floating point number. | |
| func (r RoundingMode) roundFloat(x float64) float64 { | |
| // Make rounding decision: The result mantissa is truncated ("rounded down") | |
| // by default. Decide if we need to increment, or "round up", the (unsigned) | |
| // mantissa. | |
| abs := x | |
| if x < 0 { | |
| abs = -x | |
| } | |
| i, f := math.Modf(abs) | |
| if f == 0.0 { | |
| return x | |
| } | |
| inc := false | |
| switch r { | |
| case ToNegativeInf: | |
| inc = x < 0 | |
| case ToPositiveInf: | |
| inc = x >= 0 | |
| case ToZero: | |
| // nothing to do | |
| case AwayFromZero: | |
| inc = true | |
| case ToNearestEven: | |
| // TODO: check overflow | |
| inc = f > 0.5 || f == 0.5 && int64(i)&1 != 0 | |
| case ToNearestAway: | |
| inc = f >= 0.5 | |
| case ToNearestZero: | |
| inc = f > 0.5 | |
| default: | |
| panic("unreachable") | |
| } | |
| if inc { | |
| i += 1 | |
| } | |
| if abs != x { | |
| i = -i | |
| } | |
| return i | |
| } | |
| func (x *digits) roundUp(n int) { | |
| if n < 0 || n >= len(x.Digits) { | |
| return // nothing to do | |
| } | |
| // find first digit < 9 | |
| for n > 0 && x.Digits[n-1] >= 9 { | |
| n-- | |
| } | |
| if n == 0 { | |
| // all digits are 9s => round up to 1 and update exponent | |
| x.Digits[0] = 1 // ok since len(x.Digits) > n | |
| x.Digits = x.Digits[:1] | |
| x.Exp++ | |
| return | |
| } | |
| x.Digits[n-1]++ | |
| x.Digits = x.Digits[:n] | |
| // x already trimmed | |
| } | |
| func (x *digits) roundDown(n int) { | |
| if n < 0 || n >= len(x.Digits) { | |
| return // nothing to do | |
| } | |
| x.Digits = x.Digits[:n] | |
| trim(x) | |
| } | |
| // trim cuts off any trailing zeros from x's mantissa; | |
| // they are meaningless for the value of x. | |
| func trim(x *digits) { | |
| i := len(x.Digits) | |
| for i > 0 && x.Digits[i-1] == 0 { | |
| i-- | |
| } | |
| x.Digits = x.Digits[:i] | |
| if i == 0 { | |
| x.Exp = 0 | |
| } | |
| } | |
| // A Converter converts a number into decimals according to the given rounding | |
| // criteria. | |
| type Converter interface { | |
| Convert(d *Decimal, r RoundingContext) | |
| } | |
| const ( | |
| signed = true | |
| unsigned = false | |
| ) | |
| // Convert converts the given number to the decimal representation using the | |
| // supplied RoundingContext. | |
| func (d *Decimal) Convert(r RoundingContext, number interface{}) { | |
| switch f := number.(type) { | |
| case Converter: | |
| d.clear() | |
| f.Convert(d, r) | |
| case float32: | |
| d.ConvertFloat(r, float64(f), 32) | |
| case float64: | |
| d.ConvertFloat(r, f, 64) | |
| case int: | |
| d.ConvertInt(r, signed, uint64(f)) | |
| case int8: | |
| d.ConvertInt(r, signed, uint64(f)) | |
| case int16: | |
| d.ConvertInt(r, signed, uint64(f)) | |
| case int32: | |
| d.ConvertInt(r, signed, uint64(f)) | |
| case int64: | |
| d.ConvertInt(r, signed, uint64(f)) | |
| case uint: | |
| d.ConvertInt(r, unsigned, uint64(f)) | |
| case uint8: | |
| d.ConvertInt(r, unsigned, uint64(f)) | |
| case uint16: | |
| d.ConvertInt(r, unsigned, uint64(f)) | |
| case uint32: | |
| d.ConvertInt(r, unsigned, uint64(f)) | |
| case uint64: | |
| d.ConvertInt(r, unsigned, f) | |
| default: | |
| d.NaN = true | |
| // TODO: | |
| // case string: if produced by strconv, allows for easy arbitrary pos. | |
| // case reflect.Value: | |
| // case big.Float | |
| // case big.Int | |
| // case big.Rat? | |
| // catch underlyings using reflect or will this already be done by the | |
| // message package? | |
| } | |
| } | |
| // ConvertInt converts an integer to decimals. | |
| func (d *Decimal) ConvertInt(r RoundingContext, signed bool, x uint64) { | |
| if r.Increment > 0 { | |
| // TODO: if uint64 is too large, fall back to float64 | |
| if signed { | |
| d.ConvertFloat(r, float64(int64(x)), 64) | |
| } else { | |
| d.ConvertFloat(r, float64(x), 64) | |
| } | |
| return | |
| } | |
| d.clear() | |
| if signed && int64(x) < 0 { | |
| x = uint64(-int64(x)) | |
| d.Neg = true | |
| } | |
| d.fillIntDigits(x) | |
| d.Exp = int32(len(d.Digits)) | |
| } | |
| // ConvertFloat converts a floating point number to decimals. | |
| func (d *Decimal) ConvertFloat(r RoundingContext, x float64, size int) { | |
| d.clear() | |
| if math.IsNaN(x) { | |
| d.NaN = true | |
| return | |
| } | |
| // Simple case: decimal notation | |
| if r.Increment > 0 { | |
| scale := int(r.IncrementScale) | |
| mult := 1.0 | |
| if scale >= len(scales) { | |
| mult = math.Pow(10, float64(scale)) | |
| } else { | |
| mult = scales[scale] | |
| } | |
| // We multiply x instead of dividing inc as it gives less rounding | |
| // issues. | |
| x *= mult | |
| x /= float64(r.Increment) | |
| x = r.Mode.roundFloat(x) | |
| x *= float64(r.Increment) | |
| x /= mult | |
| } | |
| abs := x | |
| if x < 0 { | |
| d.Neg = true | |
| abs = -x | |
| } | |
| if math.IsInf(abs, 1) { | |
| d.Inf = true | |
| return | |
| } | |
| // By default we get the exact decimal representation. | |
| verb := byte('g') | |
| prec := -1 | |
| // As the strconv API does not return the rounding accuracy, we can only | |
| // round using ToNearestEven. | |
| if r.Mode == ToNearestEven { | |
| if n := r.RoundSignificantDigits(); n >= 0 { | |
| prec = n | |
| } else if n = r.RoundFractionDigits(); n >= 0 { | |
| prec = n | |
| verb = 'f' | |
| } | |
| } else { | |
| // TODO: At this point strconv's rounding is imprecise to the point that | |
| // it is not usable for this purpose. | |
| // See https://github.com/golang/go/issues/21714 | |
| // If rounding is requested, we ask for a large number of digits and | |
| // round from there to simulate rounding only once. | |
| // Ideally we would have strconv export an AppendDigits that would take | |
| // a rounding mode and/or return an accuracy. Something like this would | |
| // work: | |
| // AppendDigits(dst []byte, x float64, base, size, prec int) (digits []byte, exp, accuracy int) | |
| hasPrec := r.RoundSignificantDigits() >= 0 | |
| hasScale := r.RoundFractionDigits() >= 0 | |
| if hasPrec || hasScale { | |
| // prec is the number of mantissa bits plus some extra for safety. | |
| // We need at least the number of mantissa bits as decimals to | |
| // accurately represent the floating point without rounding, as each | |
| // bit requires one more decimal to represent: 0.5, 0.25, 0.125, ... | |
| prec = 60 | |
| } | |
| } | |
| b := strconv.AppendFloat(d.Digits[:0], abs, verb, prec, size) | |
| i := 0 | |
| k := 0 | |
| beforeDot := 1 | |
| for i < len(b) { | |
| if c := b[i]; '0' <= c && c <= '9' { | |
| b[k] = c - '0' | |
| k++ | |
| d.Exp += int32(beforeDot) | |
| } else if c == '.' { | |
| beforeDot = 0 | |
| d.Exp = int32(k) | |
| } else { | |
| break | |
| } | |
| i++ | |
| } | |
| d.Digits = b[:k] | |
| if i != len(b) { | |
| i += len("e") | |
| pSign := i | |
| exp := 0 | |
| for i++; i < len(b); i++ { | |
| exp *= 10 | |
| exp += int(b[i] - '0') | |
| } | |
| if b[pSign] == '-' { | |
| exp = -exp | |
| } | |
| d.Exp = int32(exp) + 1 | |
| } | |
| } | |
| func (d *Decimal) fillIntDigits(x uint64) { | |
| if cap(d.Digits) < maxIntDigits { | |
| d.Digits = d.buf[:] | |
| } else { | |
| d.Digits = d.buf[:maxIntDigits] | |
| } | |
| i := 0 | |
| for ; x > 0; x /= 10 { | |
| d.Digits[i] = byte(x % 10) | |
| i++ | |
| } | |
| d.Digits = d.Digits[:i] | |
| for p := 0; p < i; p++ { | |
| i-- | |
| d.Digits[p], d.Digits[i] = d.Digits[i], d.Digits[p] | |
| } | |
| } | |
| var scales [70]float64 | |
| func init() { | |
| x := 1.0 | |
| for i := range scales { | |
| scales[i] = x | |
| x *= 10 | |
| } | |
| } |