mirror of
https://github.com/rclone/rclone.git
synced 2024-12-04 14:26:42 +01:00
434 lines
9.6 KiB
Go
434 lines
9.6 KiB
Go
// 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
|
|
)
|
|
|
|
// A RoundingContext indicates how a number should be converted to digits.
|
|
type RoundingContext struct {
|
|
Mode RoundingMode
|
|
Increment int32 // if > 0, round to Increment * 10^-Scale
|
|
|
|
Precision int32 // maximum number of significant digits.
|
|
Scale int32 // maximum number of decimals after the dot.
|
|
}
|
|
|
|
const maxIntDigits = 20
|
|
|
|
// A Decimal represents floating point number represented in digits of the base
|
|
// in which a number is to be displayed. 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
|
|
// 0.00123 Digits: [1, 2, 3], Exp: -2
|
|
type Decimal 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.
|
|
|
|
buf [maxIntDigits]byte
|
|
}
|
|
|
|
// normalize retuns 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 *Decimal) 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 *Decimal) 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 *Decimal) 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 *Decimal) {
|
|
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)
|
|
|
|
// 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
|
|
}
|
|
abs := x
|
|
if x < 0 {
|
|
d.Neg = true
|
|
abs = -x
|
|
}
|
|
if math.IsInf(abs, 1) {
|
|
d.Inf = true
|
|
return
|
|
}
|
|
// Simple case: decimal notation
|
|
if r.Scale > 0 || r.Increment > 0 || r.Precision == 0 {
|
|
if int(r.Scale) > len(scales) {
|
|
x *= math.Pow(10, float64(r.Scale))
|
|
} else {
|
|
x *= scales[r.Scale]
|
|
}
|
|
if r.Increment > 0 {
|
|
inc := float64(r.Increment)
|
|
x /= float64(inc)
|
|
x = r.Mode.roundFloat(x)
|
|
x *= inc
|
|
} else {
|
|
x = r.Mode.roundFloat(x)
|
|
}
|
|
d.fillIntDigits(uint64(math.Abs(x)))
|
|
d.Exp = int32(len(d.Digits)) - r.Scale
|
|
return
|
|
}
|
|
|
|
// Nasty case (for non-decimal notation).
|
|
// Asides from being inefficient, this result is also wrong as it will
|
|
// apply ToNearestEven rounding regardless of the user setting.
|
|
// TODO: expose functionality in strconv so we can avoid this hack.
|
|
// Something like this would work:
|
|
// AppendDigits(dst []byte, x float64, base, size, prec int) (digits []byte, exp, accuracy int)
|
|
// TODO: This only supports the nearest even rounding mode.
|
|
|
|
prec := int(r.Precision)
|
|
if prec > 0 {
|
|
prec--
|
|
}
|
|
b := strconv.AppendFloat(d.Digits, abs, 'e', prec, size)
|
|
i := 0
|
|
k := 0
|
|
// No need to check i < len(b) as we always have an 'e'.
|
|
for {
|
|
if c := b[i]; '0' <= c && c <= '9' {
|
|
b[k] = c - '0'
|
|
k++
|
|
} else if c != '.' {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
d.Digits = b[:k]
|
|
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
|
|
}
|
|
}
|