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