| // 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 useable 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 |
| } |
| } |