package decimal import "math/big" // Split splits an integer amount of units over n parties. // Leftover units are distributed round-robin, // from left to right. func Split(amount, unit Number, n uint) []Number { return allocate(amount, unit, n, nil) } // Allocate allocates an integer amount of units // according to a list of ratios. // Leftover units are distributed round-robin, // from left to right. func Allocate(amount, unit Number, ratios ...uint) []Number { return allocate(amount, unit, 0, ratios) } func allocate(amount, unit Number, n uint, ratios []uint) []Number { var ra, ru big.Rat fromNumber(&ra, amount) if unit != "1" { fromNumber(&ru, unit) if ru.Sign() <= 0 { panic("nonpositive unit") } ra.Quo(&ra, &ru) } if !ra.IsInt() { panic("noninteger amount") } var sum, mod big.Int var res []big.Int mod.Set(ra.Num()) if ratios == nil { res = make([]big.Int, n) sum.SetUint64(uint64(n)) for i := range n { res[i].Div(ra.Num(), &sum) mod.Sub(&mod, &res[i]) } } else { res = make([]big.Int, len(ratios)) for i, r := range ratios { res[i].SetUint64(uint64(r)) sum.Add(&sum, &res[i]) } for i := range ratios { res[i].Mul(&res[i], ra.Num()) res[i].Div(&res[i], &sum) mod.Sub(&mod, &res[i]) } } sum.SetUint64(1) for i := range mod.Uint64() { res[i].Add(&res[i], &sum) } num := make([]Number, len(res)) if unit == "1" { for i := range num { num[i] = Number(res[i].String()) } } else { for i := range num { ra.SetInt(&res[i]) num[i] = toNumber(ra.Mul(&ra, &ru)) } } return num }
package decimal import "strconv" func integerDigits(x string) int { exp := 0 dot := -1 lead := -1 loop: for i, b := range []byte(x) { switch b { case '.': if dot < 0 { dot = i } case '1', '2', '3', '4', '5', '6', '7', '8', '9': if lead < 0 { lead = i } case 'e', 'E': exp, _ = strconv.Atoi(x[i+1:]) x = x[:i] break loop } } switch { case lead < 0: return 0 case dot < 0: dot = len(x) case dot < lead: dot++ } return exp + dot - lead } func significantDigits(x string) int { zeros, digits := 0, 0 for _, b := range []byte(x) { switch b { case '0': if digits > 0 { zeros++ } case '1', '2', '3', '4', '5', '6', '7', '8', '9': digits += zeros + 1 zeros = 0 case 'e', 'E': return digits } } return digits }
// Package decimal implements arbitrary-precision decimal arithmetic. package decimal import ( "encoding/json" "fmt" "math/big" "strconv" ) // A Number is an arbitrary precision decimal number, stored as JSON text. type Number = json.Number // Int64 converts i into a decimal number. func Int64(i int64) Number { return Number(strconv.FormatInt(i, 10)) } // Float64 converts f into a decimal number. func Float64(f float64) Number { var rf big.Rat if rf.SetFloat64(f) == nil { switch { case f != f: panic("invalid decimal: NaN") case f > 0: panic("invalid decimal: +Inf") case f < 0: panic("invalid decimal: -Inf") } } return toNumber(&rf) } // Abs returns |x| (the absolute value of x). func Abs(x Number) Number { checkNumber(x) if x[0] == '-' { return x[1:] } return x } // Neg returns -x (x with its sign negated). func Neg(x Number) Number { checkNumber(x) if x[0] == '-' { return x[1:] } return "-" + x } // Add returns the sum x + y. func Add(x, y Number) Number { var rx, ry big.Rat fromNumber(&rx, x) fromNumber(&ry, y) return toNumber(rx.Add(&rx, &ry)) } // Sub returns the difference x - y. func Sub(x, y Number) Number { var rx, ry big.Rat fromNumber(&rx, x) fromNumber(&ry, y) return toNumber(rx.Sub(&rx, &ry)) } // Mul returns the product x * y. func Mul(x, y Number) Number { var rx, ry big.Rat fromNumber(&rx, x) fromNumber(&ry, y) return toNumber(rx.Mul(&rx, &ry)) } // Sum returns the sum of all n. func Sum(n ...Number) Number { var rs, rn big.Rat for _, n := range n { fromNumber(&rn, n) rs.Add(&rs, &rn) } return toNumber(&rs) } // Prod returns the product of all n. func Prod(n ...Number) Number { var rp, rn big.Rat rp.SetUint64(1) for _, n := range n { fromNumber(&rn, n) rp.Mul(&rp, &rn) } return toNumber(&rp) } // Pow returns xⁿ (the nth power of x). func Pow(x Number, n uint) Number { var rx, ry big.Rat fromNumber(&rx, x) ry.SetUint64(1) for { if n&1 != 0 { ry.Mul(&rx, &ry) } if n >>= 1; n == 0 { return toNumber(&ry) } rx.Mul(&rx, &rx) } } // Cmp compares x and y, like [cmp.Compare]. func Cmp(x, y Number) int { var rx, ry big.Rat fromNumber(&rx, x) fromNumber(&ry, y) if x == y { return 0 } return rx.Cmp(&ry) } // IsInt reports whether x is an integer. func IsInt(x Number) bool { var rx big.Rat fromNumber(&rx, x) return rx.IsInt() } // Fmt is a formatter for a decimal number. type Fmt Number // Format implements fmt.Formatter. // It accepts the formats for decimal floating-point numbers: 'e', 'E', 'f', 'F', 'g', 'G'. // The 'v' format is handled like 'g'. func (x Fmt) Format(f fmt.State, v rune) { s := string(x) prec, ok := f.Precision() if !IsValid(Number(s)) { fmt.Fprintf(f, "%%!%c(decimal=%s)", v, s) return } switch v { default: fmt.Fprintf(f, "%%!%c(decimal=%s)", v, s) return case 'e', 'E': if !ok { prec = 6 } prec += 1 case 'f', 'F': if !ok { prec = 6 } prec += integerDigits(s) case 'v', 'g', 'G': if !ok { prec = significantDigits(s) } } var fx big.Float prec = max(0, prec) // prec in digits, multiply by log₂(10) for bits fx.SetPrec((107*uint(prec) + 31) / 32) fx.Parse(s, 10) fx.Format(f, v) } func toNumber(x *big.Rat) Number { n, exact := x.FloatPrec() if exact { return Number(x.FloatString(n)) } panic("inexact decimal") } func fromNumber(rx *big.Rat, x Number) { if !IsValid(x) { panic("invalid decimal: " + string(x)) } _, ok := rx.SetString(string(x)) if ok { return } panic("decimal overflow: " + string(x)) } func checkNumber(x Number) string { if IsValid(x) { return string(x) } panic("invalid decimal: " + string(x)) }
package decimal import "math/big" // Trunc rounds x toward zero, // to a multiple of unit. func Trunc(x, unit Number) Number { return scale(x, unit, func(rx *big.Rat) { nx := rx.Num() dx := rx.Denom() nx.Quo(nx, dx) dx.SetUint64(1) }) } // Floor returns the greatest multiple of unit // less than or equal to x. func Floor(x, unit Number) Number { return scale(x, unit, func(rx *big.Rat) { nx := rx.Num() dx := rx.Denom() nx.Div(nx, dx) dx.SetUint64(1) }) } // Ceil returns the least multiple of unit // greater than or equal to x. func Ceil(x, unit Number) Number { return scale(x, unit, func(rx *big.Rat) { nx := rx.Num() dx := rx.Denom() nx.Neg(nx) nx.Div(nx, dx) nx.Neg(nx) dx.SetUint64(1) }) } // Round rounds x to the nearest multiple of unit, // with ties away from zero. func Round(x, unit Number) Number { return scale(x, unit, tiesAway.round) } // RoundToEven rounds x to the nearest multiple of unit, // with ties to an even multiple of unit. func RoundToEven(x, unit Number) Number { return scale(x, unit, tiesToEven.round) } type rounder int8 const ( tiesAway rounder = iota tiesToEven ) func (r rounder) round(rx *big.Rat) { nx := rx.Num() dx := rx.Denom() neg := nx.Sign() < 0 nx.Abs(nx) var mx big.Int nx.QuoRem(nx, dx, &mx) cmp := mx.Lsh(&mx, 1).CmpAbs(dx) dx.SetUint64(1) if cmp > 0 || cmp == 0 && (r == tiesAway || nx.Bit(0) != 0) { nx.Add(nx, dx) } if neg { nx.Neg(nx) } } func scale(x, unit Number, round func(*big.Rat)) Number { var rx, ru big.Rat fromNumber(&rx, x) if unit != "1" { fromNumber(&ru, unit) if ru.Sign() <= 0 { panic("nonpositive unit") } rx.Quo(&rx, &ru) } if rx.IsInt() { return x } round(&rx) if unit == "1" { return Number(rx.Num().String()) } else { return toNumber(rx.Mul(&rx, &ru)) } }
package decimal // IsValid reports whether x is a valid JSON number literal. func IsValid(x Number) bool { var state byte for _, d := range []byte(x) { switch state { case 0: switch d { case '0', '-': state = d case '1', '2', '3', '4', '5', '6', '7', '8', '9': state = '1' default: return false } case '-': switch d { case '0': state = '0' case '1', '2', '3', '4', '5', '6', '7', '8', '9': state = '1' default: return false } case '0': switch d { case '.': state = '.' case 'e', 'E': state = 'e' default: return false } case '1': switch d { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': continue case '.': state = '.' case 'e', 'E': state = 'e' default: return false } case '.': switch d { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': state = '2' default: return false } case '2': switch d { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': continue case 'e', 'E': state = 'e' default: return false } case 'e': switch d { case '-', '+': state = '+' case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': state = '3' default: return false } case '+': switch d { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': state = '3' default: return false } case '3': switch d { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': continue default: return false } } } switch state { case '0', '1', '2', '3': return true default: return false } }