mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
1346 lines
25 KiB
Go
1346 lines
25 KiB
Go
//go:build dnum
|
|
|
|
package fixedpoint
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/bits"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Value struct {
|
|
coef uint64
|
|
sign int8
|
|
exp int
|
|
}
|
|
|
|
const (
|
|
signPosInf = +2
|
|
signPos = +1
|
|
signZero = 0
|
|
signNeg = -1
|
|
signNegInf = -2
|
|
coefMin = 1000_0000_0000_0000
|
|
coefMax = 9999_9999_9999_9999
|
|
digitsMax = 16
|
|
shiftMax = digitsMax - 1
|
|
// to switch between scientific notion and normal presentation format
|
|
maxLeadingZeros = 19
|
|
)
|
|
|
|
// common values
|
|
var (
|
|
Zero = Value{}
|
|
One = Value{1000_0000_0000_0000, signPos, 1}
|
|
NegOne = Value{1000_0000_0000_0000, signNeg, 1}
|
|
PosInf = Value{1, signPosInf, 0}
|
|
NegInf = Value{1, signNegInf, 0}
|
|
)
|
|
|
|
var pow10f = [...]float64{
|
|
1,
|
|
10,
|
|
100,
|
|
1000,
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
100000000,
|
|
1000000000,
|
|
10000000000,
|
|
100000000000,
|
|
1000000000000,
|
|
10000000000000,
|
|
100000000000000,
|
|
1000000000000000,
|
|
10000000000000000,
|
|
100000000000000000,
|
|
1000000000000000000,
|
|
10000000000000000000,
|
|
100000000000000000000}
|
|
|
|
var pow10 = [...]uint64{
|
|
1,
|
|
10,
|
|
100,
|
|
1000,
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
100000000,
|
|
1000000000,
|
|
10000000000,
|
|
100000000000,
|
|
1000000000000,
|
|
10000000000000,
|
|
100000000000000,
|
|
1000000000000000,
|
|
10000000000000000,
|
|
100000000000000000,
|
|
1000000000000000000}
|
|
|
|
var halfpow10 = [...]uint64{
|
|
0,
|
|
5,
|
|
50,
|
|
500,
|
|
5000,
|
|
50000,
|
|
500000,
|
|
5000000,
|
|
50000000,
|
|
500000000,
|
|
5000000000,
|
|
50000000000,
|
|
500000000000,
|
|
5000000000000,
|
|
50000000000000,
|
|
500000000000000,
|
|
5000000000000000,
|
|
50000000000000000,
|
|
500000000000000000,
|
|
5000000000000000000}
|
|
|
|
func min(a int, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func max(a int, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (v Value) Value() (driver.Value, error) {
|
|
return v.Float64(), nil
|
|
}
|
|
|
|
// NewFromInt returns a Value for an int
|
|
func NewFromInt(n int64) Value {
|
|
if n == 0 {
|
|
return Zero
|
|
}
|
|
// n0 := n
|
|
sign := int8(signPos)
|
|
if n < 0 {
|
|
n = -n
|
|
sign = signNeg
|
|
}
|
|
return newNoSignCheck(sign, uint64(n), digitsMax)
|
|
}
|
|
|
|
const log2of10 = 3.32192809488736234
|
|
|
|
// NewFromFloat converts a float64 to a Value
|
|
func NewFromFloat(f float64) Value {
|
|
switch {
|
|
case math.IsInf(f, +1):
|
|
return PosInf
|
|
case math.IsInf(f, -1):
|
|
return NegInf
|
|
case math.IsNaN(f):
|
|
panic("value.NewFromFloat can't convert NaN")
|
|
}
|
|
|
|
if f == 0 {
|
|
return Zero
|
|
}
|
|
|
|
sign := int8(signPos)
|
|
if f < 0 {
|
|
f = -f
|
|
sign = signNeg
|
|
}
|
|
n := uint64(f)
|
|
if float64(n) == f {
|
|
return newNoSignCheck(sign, n, digitsMax)
|
|
}
|
|
_, e := math.Frexp(f)
|
|
e = int(float32(e) / log2of10)
|
|
c := uint64(f/math.Pow10(e-16) + 0.5)
|
|
return newNoSignCheck(sign, c, e)
|
|
}
|
|
|
|
// Raw constructs a Value without normalizing - arguments must be valid.
|
|
// Used by SuValue Unpack
|
|
func Raw(sign int8, coef uint64, exp int) Value {
|
|
return Value{coef, sign, int(exp)}
|
|
}
|
|
|
|
func newNoSignCheck(sign int8, coef uint64, exp int) Value {
|
|
atmax := false
|
|
for coef > coefMax {
|
|
coef = (coef + 5) / 10
|
|
exp++
|
|
atmax = true
|
|
}
|
|
|
|
if !atmax {
|
|
p := maxShift(coef)
|
|
coef *= pow10[p]
|
|
exp -= p
|
|
}
|
|
return Value{coef, sign, exp}
|
|
}
|
|
|
|
// New constructs a Value, maximizing coef and handling exp out of range
|
|
// Used to normalize results of operations
|
|
func New(sign int8, coef uint64, exp int) Value {
|
|
if sign == 0 || coef == 0 {
|
|
return Zero
|
|
} else if sign == signPosInf {
|
|
return PosInf
|
|
} else if sign == signNegInf {
|
|
return NegInf
|
|
} else {
|
|
atmax := false
|
|
for coef > coefMax {
|
|
coef = (coef + 5) / 10
|
|
exp++
|
|
atmax = true
|
|
}
|
|
|
|
if !atmax {
|
|
p := maxShift(coef)
|
|
coef *= pow10[p]
|
|
exp -= p
|
|
}
|
|
return Value{coef, sign, exp}
|
|
}
|
|
}
|
|
|
|
func maxShift(x uint64) int {
|
|
i := ilog10(x)
|
|
if i > shiftMax {
|
|
return 0
|
|
}
|
|
return shiftMax - i
|
|
}
|
|
|
|
func ilog10(x uint64) int {
|
|
// based on Hacker's Delight
|
|
if x == 0 {
|
|
return 0
|
|
}
|
|
y := (19 * (63 - bits.LeadingZeros64(x))) >> 6
|
|
if y < 18 && x >= pow10[y+1] {
|
|
y++
|
|
}
|
|
return y
|
|
}
|
|
|
|
func Inf(sign int8) Value {
|
|
switch {
|
|
case sign < 0:
|
|
return NegInf
|
|
case sign > 0:
|
|
return PosInf
|
|
default:
|
|
return Zero
|
|
}
|
|
}
|
|
|
|
func (dn Value) FormatString(prec int) string {
|
|
if dn.sign == 0 {
|
|
if prec <= 0 {
|
|
return "0"
|
|
} else {
|
|
return "0." + strings.Repeat("0", prec)
|
|
}
|
|
}
|
|
sign := ""
|
|
if dn.sign < 0 {
|
|
sign = "-"
|
|
}
|
|
if dn.IsInf() {
|
|
return sign + "inf"
|
|
}
|
|
digits := getDigits(dn.coef)
|
|
nd := len(digits)
|
|
e := int(dn.exp) - nd
|
|
if -maxLeadingZeros <= dn.exp && dn.exp <= 0 {
|
|
// decimal to the left
|
|
if prec+e+nd > 0 {
|
|
return sign + "0." + strings.Repeat("0", -e-nd) + digits[:min(prec+e+nd, nd)] + strings.Repeat("0", max(0, prec-nd+e+nd))
|
|
} else if -e-nd > 0 {
|
|
return "0." + strings.Repeat("0", -e-nd)
|
|
} else {
|
|
return "0"
|
|
}
|
|
} else if -nd < e && e <= -1 {
|
|
// decimal within
|
|
dec := nd + e
|
|
decimals := digits[dec:min(dec+prec, nd)]
|
|
return sign + digits[:dec] + "." + decimals + strings.Repeat("0", max(0, prec-len(decimals)))
|
|
} else if 0 < dn.exp && dn.exp <= digitsMax {
|
|
// decimal to the right
|
|
if prec > 0 {
|
|
return sign + digits + strings.Repeat("0", e) + "." + strings.Repeat("0", prec)
|
|
} else {
|
|
return sign + digits + strings.Repeat("0", e)
|
|
}
|
|
} else {
|
|
// scientific notation
|
|
after := ""
|
|
if nd > 1 {
|
|
after = "." + digits[1:min(1+prec, nd)] + strings.Repeat("0", max(0, min(1+prec, nd)-1-prec))
|
|
}
|
|
return sign + digits[:1] + after + "e" + strconv.Itoa(int(dn.exp-1))
|
|
}
|
|
}
|
|
|
|
// String returns a string representation of the Value
|
|
func (dn Value) String() string {
|
|
if dn.sign == 0 {
|
|
return "0"
|
|
}
|
|
sign := ""
|
|
if dn.sign < 0 {
|
|
sign = "-"
|
|
}
|
|
if dn.IsInf() {
|
|
return sign + "inf"
|
|
}
|
|
digits := getDigits(dn.coef)
|
|
nd := len(digits)
|
|
e := int(dn.exp) - nd
|
|
if -maxLeadingZeros <= dn.exp && dn.exp <= 0 {
|
|
// decimal to the left
|
|
return sign + "0." + strings.Repeat("0", -e-nd) + digits
|
|
} else if -nd < e && e <= -1 {
|
|
// decimal within
|
|
dec := nd + e
|
|
return sign + digits[:dec] + "." + digits[dec:]
|
|
} else if 0 < dn.exp && dn.exp <= digitsMax {
|
|
// decimal to the right
|
|
return sign + digits + strings.Repeat("0", e)
|
|
} else {
|
|
// scientific notation
|
|
after := ""
|
|
if nd > 1 {
|
|
after = "." + digits[1:]
|
|
}
|
|
return sign + digits[:1] + after + "e" + strconv.Itoa(int(dn.exp-1))
|
|
}
|
|
}
|
|
|
|
func (dn Value) Percentage() string {
|
|
if dn.sign == 0 {
|
|
return "0%"
|
|
}
|
|
sign := ""
|
|
if dn.sign < 0 {
|
|
sign = "-"
|
|
}
|
|
if dn.IsInf() {
|
|
return sign + "inf%"
|
|
}
|
|
digits := getDigits(dn.coef)
|
|
nd := len(digits)
|
|
e := int(dn.exp) - nd + 2
|
|
|
|
if -maxLeadingZeros <= dn.exp && dn.exp <= -2 {
|
|
// decimal to the left
|
|
return sign + "0." + strings.Repeat("0", -e-nd) + digits + "%"
|
|
} else if -nd < e && e <= -1 {
|
|
// decimal within
|
|
dec := nd + e
|
|
return sign + digits[:dec] + "." + digits[dec:] + "%"
|
|
} else if -2 < dn.exp && dn.exp <= digitsMax {
|
|
// decimal to the right
|
|
return sign + digits + strings.Repeat("0", e) + "%"
|
|
} else {
|
|
// scientific notation
|
|
after := ""
|
|
if nd > 1 {
|
|
after = "." + digits[1:]
|
|
}
|
|
return sign + digits[:1] + after + "e" + strconv.Itoa(int(dn.exp-1)) + "%"
|
|
}
|
|
}
|
|
|
|
func (dn Value) FormatPercentage(prec int) string {
|
|
if dn.sign == 0 {
|
|
if prec <= 0 {
|
|
return "0"
|
|
} else {
|
|
return "0." + strings.Repeat("0", prec)
|
|
}
|
|
}
|
|
sign := ""
|
|
if dn.sign < 0 {
|
|
sign = "-"
|
|
}
|
|
if dn.IsInf() {
|
|
return sign + "inf"
|
|
}
|
|
digits := getDigits(dn.coef)
|
|
nd := len(digits)
|
|
exp := dn.exp + 2
|
|
e := int(exp) - nd
|
|
|
|
if -maxLeadingZeros <= exp && exp <= 0 {
|
|
// decimal to the left
|
|
if prec+e+nd > 0 {
|
|
return sign + "0." + strings.Repeat("0", -e-nd) + digits[:min(prec+e+nd, nd)] + strings.Repeat("0", max(0, prec-nd+e+nd)) + "%"
|
|
} else if -e-nd > 0 {
|
|
return "0." + strings.Repeat("0", -e-nd) + "%"
|
|
} else {
|
|
return "0"
|
|
}
|
|
} else if -nd < e && e <= -1 {
|
|
// decimal within
|
|
dec := nd + e
|
|
decimals := digits[dec:min(dec+prec, nd)]
|
|
return sign + digits[:dec] + "." + decimals + strings.Repeat("0", max(0, prec-len(decimals))) + "%"
|
|
} else if 0 < exp && exp <= digitsMax {
|
|
// decimal to the right
|
|
if prec > 0 {
|
|
return sign + digits + strings.Repeat("0", e) + "." + strings.Repeat("0", prec) + "%"
|
|
} else {
|
|
return sign + digits + strings.Repeat("0", e) + "%"
|
|
}
|
|
} else {
|
|
// scientific notation
|
|
after := ""
|
|
if nd > 1 {
|
|
after = "." + digits[1:min(1+prec, nd)] + strings.Repeat("0", max(0, min(1+prec, nd)-1-prec))
|
|
}
|
|
return sign + digits[:1] + after + "e" + strconv.Itoa(int(exp-1)) + "%"
|
|
}
|
|
}
|
|
|
|
func (dn Value) SignedPercentage() string {
|
|
if dn.Sign() >= 0 {
|
|
return "+" + dn.Percentage()
|
|
}
|
|
return dn.Percentage()
|
|
}
|
|
|
|
// get digit length
|
|
func (a Value) NumDigits() int {
|
|
i := shiftMax
|
|
coef := a.coef
|
|
nd := 0
|
|
for coef != 0 && coef < pow10[i] {
|
|
i--
|
|
}
|
|
for coef != 0 {
|
|
coef %= pow10[i]
|
|
i--
|
|
nd++
|
|
}
|
|
return nd
|
|
}
|
|
|
|
// alias of Exp
|
|
func (a Value) NumIntDigits() int {
|
|
return a.exp
|
|
}
|
|
|
|
// get fractional digits
|
|
func (a Value) NumFractionalDigits() int {
|
|
nd := a.NumDigits()
|
|
return nd - a.exp
|
|
}
|
|
|
|
func getDigits(coef uint64) string {
|
|
var digits [digitsMax]byte
|
|
i := shiftMax
|
|
nd := 0
|
|
for coef != 0 {
|
|
digits[nd] = byte('0' + (coef / pow10[i]))
|
|
coef %= pow10[i]
|
|
nd++
|
|
i--
|
|
}
|
|
return string(digits[:nd])
|
|
}
|
|
|
|
func (v *Value) Scan(src interface{}) error {
|
|
var err error
|
|
switch d := src.(type) {
|
|
case int64:
|
|
*v = NewFromInt(d)
|
|
return nil
|
|
case float64:
|
|
*v = NewFromFloat(d)
|
|
return nil
|
|
case []byte:
|
|
*v, err = NewFromString(string(d))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
default:
|
|
}
|
|
return fmt.Errorf("fixedpoint.Value scan error, type %T is not supported, value: %+v", src, src)
|
|
}
|
|
|
|
// NewFromString parses a numeric string and returns a Value representation.
|
|
func NewFromString(s string) (Value, error) {
|
|
length := len(s)
|
|
if length == 0 {
|
|
return Zero, nil
|
|
}
|
|
isPercentage := s[length-1] == '%'
|
|
if isPercentage {
|
|
s = s[:length-1]
|
|
}
|
|
r := &reader{s, 0}
|
|
sign := r.getSign()
|
|
if r.matchStrIgnoreCase("inf") {
|
|
return Inf(sign), nil
|
|
}
|
|
coef, exp := r.getCoef()
|
|
exp += r.getExp()
|
|
if r.len() != 0 { // didn't consume entire string
|
|
return Zero, errors.New("invalid number")
|
|
} else if coef == 0 || exp < math.MinInt8 {
|
|
return Zero, nil
|
|
} else if exp > math.MaxInt8 {
|
|
return Inf(sign), nil
|
|
}
|
|
if isPercentage {
|
|
exp -= 2
|
|
}
|
|
atmax := false
|
|
for coef > coefMax {
|
|
coef = (coef + 5) / 10
|
|
exp++
|
|
atmax = true
|
|
}
|
|
|
|
if !atmax {
|
|
p := maxShift(coef)
|
|
coef *= pow10[p]
|
|
exp -= p
|
|
}
|
|
// check(coefMin <= coef && coef <= coefMax)
|
|
return Value{coef, sign, exp}, nil
|
|
}
|
|
|
|
func MustNewFromString(input string) Value {
|
|
v, err := NewFromString(input)
|
|
if err != nil {
|
|
panic(fmt.Errorf("cannot parse %s into fixedpoint, error: %s", input, err.Error()))
|
|
}
|
|
return v
|
|
}
|
|
|
|
func NewFromBytes(s []byte) (Value, error) {
|
|
length := len(s)
|
|
if length == 0 {
|
|
return Zero, nil
|
|
}
|
|
isPercentage := s[length-1] == '%'
|
|
if isPercentage {
|
|
s = s[:length-1]
|
|
}
|
|
r := &readerBytes{s, 0}
|
|
sign := r.getSign()
|
|
if r.matchStrIgnoreCase("inf") {
|
|
return Inf(sign), nil
|
|
}
|
|
coef, exp := r.getCoef()
|
|
exp += r.getExp()
|
|
if r.len() != 0 { // didn't consume entire string
|
|
return Zero, errors.New("invalid number")
|
|
} else if coef == 0 || exp < math.MinInt8 {
|
|
return Zero, nil
|
|
} else if exp > math.MaxInt8 {
|
|
return Inf(sign), nil
|
|
}
|
|
if isPercentage {
|
|
exp -= 2
|
|
}
|
|
atmax := false
|
|
for coef > coefMax {
|
|
coef = (coef + 5) / 10
|
|
exp++
|
|
atmax = true
|
|
}
|
|
|
|
if !atmax {
|
|
p := maxShift(coef)
|
|
coef *= pow10[p]
|
|
exp -= p
|
|
}
|
|
// check(coefMin <= coef && coef <= coefMax)
|
|
return Value{coef, sign, exp}, nil
|
|
}
|
|
|
|
func MustNewFromBytes(input []byte) Value {
|
|
v, err := NewFromBytes(input)
|
|
if err != nil {
|
|
panic(fmt.Errorf("cannot parse %s into fixedpoint, error: %s", input, err.Error()))
|
|
}
|
|
return v
|
|
}
|
|
|
|
// TODO: refactor by interface
|
|
|
|
type readerBytes struct {
|
|
s []byte
|
|
i int
|
|
}
|
|
|
|
func (r *readerBytes) cur() byte {
|
|
if r.i >= len(r.s) {
|
|
return 0
|
|
}
|
|
return byte(r.s[r.i])
|
|
}
|
|
|
|
func (r *readerBytes) prev() byte {
|
|
if r.i == 0 {
|
|
return 0
|
|
}
|
|
return byte(r.s[r.i-1])
|
|
}
|
|
|
|
func (r *readerBytes) len() int {
|
|
return len(r.s) - r.i
|
|
}
|
|
|
|
func (r *readerBytes) match(c byte) bool {
|
|
if r.cur() == c {
|
|
r.i++
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r *readerBytes) matchDigit() bool {
|
|
c := r.cur()
|
|
if '0' <= c && c <= '9' {
|
|
r.i++
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r *readerBytes) matchStrIgnoreCase(pre string) bool {
|
|
pre = strings.ToLower(pre)
|
|
boundary := r.i + len(pre)
|
|
if boundary > len(r.s) {
|
|
return false
|
|
}
|
|
for i, c := range bytes.ToLower(r.s[r.i:boundary]) {
|
|
if pre[i] != c {
|
|
return false
|
|
}
|
|
}
|
|
r.i = boundary
|
|
return true
|
|
}
|
|
|
|
func (r *readerBytes) getSign() int8 {
|
|
if r.match('-') {
|
|
return int8(signNeg)
|
|
}
|
|
r.match('+')
|
|
return int8(signPos)
|
|
}
|
|
|
|
func (r *readerBytes) getCoef() (uint64, int) {
|
|
digits := false
|
|
beforeDecimal := true
|
|
for r.match('0') {
|
|
digits = true
|
|
}
|
|
if r.cur() == '.' && r.len() > 1 {
|
|
digits = false
|
|
}
|
|
n := uint64(0)
|
|
exp := 0
|
|
p := shiftMax
|
|
for {
|
|
c := r.cur()
|
|
if r.matchDigit() {
|
|
digits = true
|
|
// ignore extra decimal places
|
|
if c != '0' && p >= 0 {
|
|
n += uint64(c-'0') * pow10[p]
|
|
}
|
|
p--
|
|
} else if beforeDecimal {
|
|
// decimal point or end
|
|
exp = shiftMax - p
|
|
if !r.match('.') {
|
|
break
|
|
}
|
|
beforeDecimal = false
|
|
if !digits {
|
|
for r.match('0') {
|
|
digits = true
|
|
exp--
|
|
}
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
if !digits {
|
|
panic("numbers require at least one digit")
|
|
}
|
|
return n, exp
|
|
}
|
|
|
|
func (r *readerBytes) getExp() int {
|
|
e := 0
|
|
if r.match('e') || r.match('E') {
|
|
esign := r.getSign()
|
|
for r.matchDigit() {
|
|
e = e*10 + int(r.prev()-'0')
|
|
}
|
|
e *= int(esign)
|
|
}
|
|
return e
|
|
}
|
|
|
|
type reader struct {
|
|
s string
|
|
i int
|
|
}
|
|
|
|
func (r *reader) cur() byte {
|
|
if r.i >= len(r.s) {
|
|
return 0
|
|
}
|
|
return byte(r.s[r.i])
|
|
}
|
|
|
|
func (r *reader) prev() byte {
|
|
if r.i == 0 {
|
|
return 0
|
|
}
|
|
return byte(r.s[r.i-1])
|
|
}
|
|
|
|
func (r *reader) len() int {
|
|
return len(r.s) - r.i
|
|
}
|
|
|
|
func (r *reader) match(c byte) bool {
|
|
if r.cur() == c {
|
|
r.i++
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r *reader) matchDigit() bool {
|
|
c := r.cur()
|
|
if '0' <= c && c <= '9' {
|
|
r.i++
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r *reader) matchStrIgnoreCase(pre string) bool {
|
|
boundary := r.i + len(pre)
|
|
if boundary > len(r.s) {
|
|
return false
|
|
}
|
|
data := strings.ToLower(r.s[r.i:boundary])
|
|
pre = strings.ToLower(pre)
|
|
if data == pre {
|
|
r.i = boundary
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r *reader) getSign() int8 {
|
|
if r.match('-') {
|
|
return int8(signNeg)
|
|
}
|
|
r.match('+')
|
|
return int8(signPos)
|
|
}
|
|
|
|
func (r *reader) getCoef() (uint64, int) {
|
|
digits := false
|
|
beforeDecimal := true
|
|
for r.match('0') {
|
|
digits = true
|
|
}
|
|
if r.cur() == '.' && r.len() > 1 {
|
|
digits = false
|
|
}
|
|
n := uint64(0)
|
|
exp := 0
|
|
p := shiftMax
|
|
for {
|
|
c := r.cur()
|
|
if r.matchDigit() {
|
|
digits = true
|
|
// ignore extra decimal places
|
|
if c != '0' && p >= 0 {
|
|
n += uint64(c-'0') * pow10[p]
|
|
}
|
|
p--
|
|
} else if beforeDecimal {
|
|
// decimal point or end
|
|
exp = shiftMax - p
|
|
if !r.match('.') {
|
|
break
|
|
}
|
|
beforeDecimal = false
|
|
if !digits {
|
|
for r.match('0') {
|
|
digits = true
|
|
exp--
|
|
}
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
if !digits {
|
|
panic("numbers require at least one digit")
|
|
}
|
|
return n, exp
|
|
}
|
|
|
|
func (r *reader) getExp() int {
|
|
e := 0
|
|
if r.match('e') || r.match('E') {
|
|
esign := r.getSign()
|
|
for r.matchDigit() {
|
|
e = e*10 + int(r.prev()-'0')
|
|
}
|
|
e *= int(esign)
|
|
}
|
|
return e
|
|
}
|
|
|
|
// end of FromStr ---------------------------------------------------
|
|
|
|
// IsInf returns true if a Value is positive or negative infinite
|
|
func (dn Value) IsInf() bool {
|
|
return dn.sign == signPosInf || dn.sign == signNegInf
|
|
}
|
|
|
|
// IsZero returns true if a Value is zero
|
|
func (dn Value) IsZero() bool {
|
|
return dn.sign == signZero
|
|
}
|
|
|
|
// Float64 converts a Value to float64
|
|
func (dn Value) Float64() float64 {
|
|
if dn.IsInf() {
|
|
return math.Inf(int(dn.sign))
|
|
}
|
|
g := float64(dn.coef)
|
|
if dn.sign == signNeg {
|
|
g = -g
|
|
}
|
|
i := int(dn.exp) - digitsMax
|
|
return g * math.Pow(10, float64(i))
|
|
}
|
|
|
|
// Int64 converts a Value to an int64, returning whether it was convertible
|
|
func (dn Value) Int64() int64 {
|
|
if dn.sign == 0 {
|
|
return 0
|
|
}
|
|
if dn.sign != signNegInf && dn.sign != signPosInf {
|
|
if 0 < dn.exp && dn.exp < digitsMax {
|
|
return int64(dn.sign) * int64(dn.coef/pow10[digitsMax-dn.exp])
|
|
} else if dn.exp <= 0 && dn.coef != 0 {
|
|
result := math.Log10(float64(dn.coef)) - float64(digitsMax) + float64(dn.exp)
|
|
return int64(dn.sign) * int64(math.Pow(10, result))
|
|
}
|
|
if dn.exp == digitsMax {
|
|
return int64(dn.sign) * int64(dn.coef)
|
|
}
|
|
if dn.exp == digitsMax+1 {
|
|
return int64(dn.sign) * (int64(dn.coef) * 10)
|
|
}
|
|
if dn.exp == digitsMax+2 {
|
|
return int64(dn.sign) * (int64(dn.coef) * 100)
|
|
}
|
|
if dn.exp == digitsMax+3 && dn.coef < math.MaxInt64/1000 {
|
|
return int64(dn.sign) * (int64(dn.coef) * 1000)
|
|
}
|
|
}
|
|
panic("unable to convert Value to int64")
|
|
}
|
|
|
|
func (dn Value) Int() int {
|
|
// if int is int64, this is a nop
|
|
n := dn.Int64()
|
|
if int64(int(n)) != n {
|
|
panic("unable to convert Value to int32")
|
|
}
|
|
return int(n)
|
|
}
|
|
|
|
// Sign returns -1 for negative, 0 for zero, and +1 for positive
|
|
func (dn Value) Sign() int {
|
|
return int(dn.sign)
|
|
}
|
|
|
|
// Coef returns the coefficient
|
|
func (dn Value) Coef() uint64 {
|
|
return dn.coef
|
|
}
|
|
|
|
// Exp returns the exponent
|
|
func (dn Value) Exp() int {
|
|
return int(dn.exp)
|
|
}
|
|
|
|
// Frac returns the fractional portion, i.e. x - x.Int()
|
|
func (dn Value) Frac() Value {
|
|
if dn.sign == 0 || dn.sign == signNegInf || dn.sign == signPosInf ||
|
|
dn.exp >= digitsMax {
|
|
return Zero
|
|
}
|
|
if dn.exp <= 0 {
|
|
return dn
|
|
}
|
|
frac := dn.coef % pow10[digitsMax-dn.exp]
|
|
if frac == dn.coef {
|
|
return dn
|
|
}
|
|
return New(dn.sign, frac, int(dn.exp))
|
|
}
|
|
|
|
type RoundingMode int
|
|
|
|
const (
|
|
Up RoundingMode = iota
|
|
Down
|
|
HalfUp
|
|
)
|
|
|
|
// Trunc returns the integer portion (truncating any fractional part)
|
|
func (dn Value) Trunc() Value {
|
|
return dn.integer(Down)
|
|
}
|
|
|
|
func (dn Value) integer(mode RoundingMode) Value {
|
|
if dn.sign == 0 || dn.sign == signNegInf || dn.sign == signPosInf ||
|
|
dn.exp >= digitsMax {
|
|
return dn
|
|
}
|
|
if dn.exp <= 0 {
|
|
if mode == Up ||
|
|
(mode == HalfUp && dn.exp == 0 && dn.coef >= One.coef*5) {
|
|
return New(dn.sign, One.coef, int(dn.exp)+1)
|
|
}
|
|
return Zero
|
|
}
|
|
e := digitsMax - dn.exp
|
|
frac := dn.coef % pow10[e]
|
|
if frac == 0 {
|
|
return dn
|
|
}
|
|
i := dn.coef - frac
|
|
if (mode == Up && frac > 0) || (mode == HalfUp && frac >= halfpow10[e]) {
|
|
return New(dn.sign, i+pow10[e], int(dn.exp)) // normalize
|
|
}
|
|
return Value{i, dn.sign, dn.exp}
|
|
}
|
|
|
|
func (dn Value) Floor() Value {
|
|
return dn.Round(0, Down)
|
|
}
|
|
|
|
func (dn Value) Round(r int, mode RoundingMode) Value {
|
|
if dn.sign == 0 || dn.sign == signNegInf || dn.sign == signPosInf ||
|
|
r >= digitsMax {
|
|
return dn
|
|
}
|
|
if r <= -digitsMax {
|
|
return Zero
|
|
}
|
|
n := New(dn.sign, dn.coef, int(dn.exp)+r) // multiply by 10^r
|
|
n = n.integer(mode)
|
|
if n.sign == signPos || n.sign == signNeg { // i.e. not zero or inf
|
|
return New(n.sign, n.coef, int(n.exp)-r)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// arithmetic operations -------------------------------------------------------
|
|
|
|
// Neg returns the Value negated i.e. sign reversed
|
|
func (dn Value) Neg() Value {
|
|
return Value{dn.coef, -dn.sign, dn.exp}
|
|
}
|
|
|
|
// Abs returns the Value with a positive sign
|
|
func (dn Value) Abs() Value {
|
|
if dn.sign < 0 {
|
|
return Value{dn.coef, -dn.sign, dn.exp}
|
|
}
|
|
return dn
|
|
}
|
|
|
|
// Equal returns true if two Value's are equal
|
|
func Equal(x, y Value) bool {
|
|
return x.sign == y.sign && x.exp == y.exp && x.coef == y.coef
|
|
}
|
|
|
|
func (x Value) Eq(y Value) bool {
|
|
return Equal(x, y)
|
|
}
|
|
|
|
func Max(x, y Value) Value {
|
|
if Compare(x, y) > 0 {
|
|
return x
|
|
}
|
|
return y
|
|
}
|
|
|
|
func Min(x, y Value) Value {
|
|
if Compare(x, y) < 0 {
|
|
return x
|
|
}
|
|
return y
|
|
}
|
|
|
|
// Compare compares two Value's returning -1 for <, 0 for ==, +1 for >
|
|
func Compare(x, y Value) int {
|
|
switch {
|
|
case x.sign < y.sign:
|
|
return -1
|
|
case x.sign > y.sign:
|
|
return 1
|
|
case x == y:
|
|
return 0
|
|
}
|
|
sign := int(x.sign)
|
|
switch {
|
|
case sign == 0 || sign == signNegInf || sign == signPosInf:
|
|
return 0
|
|
case x.exp < y.exp:
|
|
return -sign
|
|
case x.exp > y.exp:
|
|
return +sign
|
|
case x.coef < y.coef:
|
|
return -sign
|
|
case x.coef > y.coef:
|
|
return +sign
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func (x Value) Compare(y Value) int {
|
|
return Compare(x, y)
|
|
}
|
|
|
|
func (v Value) MarshalYAML() (interface{}, error) {
|
|
return v.FormatString(8), nil
|
|
}
|
|
|
|
func (v *Value) UnmarshalYAML(unmarshal func(a interface{}) error) (err error) {
|
|
var f float64
|
|
if err = unmarshal(&f); err == nil {
|
|
*v = NewFromFloat(f)
|
|
return
|
|
}
|
|
var i int64
|
|
if err = unmarshal(&i); err == nil {
|
|
*v = NewFromInt(i)
|
|
return
|
|
}
|
|
|
|
var s string
|
|
if err = unmarshal(&s); err == nil {
|
|
nv, err2 := NewFromString(s)
|
|
if err2 == nil {
|
|
*v = nv
|
|
return
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// FIXME: should we limit to 8 prec?
|
|
func (v Value) MarshalJSON() ([]byte, error) {
|
|
if v.IsInf() {
|
|
return []byte("\"" + v.String() + "\""), nil
|
|
}
|
|
return []byte(v.FormatString(8)), nil
|
|
}
|
|
|
|
func (v *Value) UnmarshalJSON(data []byte) error {
|
|
// FIXME: do we need to compare {}, [], "", or "null"?
|
|
if bytes.Compare(data, []byte{'n', 'u', 'l', 'l'}) == 0 {
|
|
*v = Zero
|
|
return nil
|
|
}
|
|
if len(data) == 0 {
|
|
*v = Zero
|
|
return nil
|
|
}
|
|
var err error
|
|
if data[0] == '"' {
|
|
data = data[1 : len(data)-1]
|
|
}
|
|
if *v, err = NewFromBytes(data); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Must(v Value, err error) Value {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// v * 10^(exp)
|
|
func (v Value) MulExp(exp int) Value {
|
|
return Value{v.coef, v.sign, v.exp + exp}
|
|
}
|
|
|
|
// Sub returns the difference of two Value's
|
|
func Sub(x, y Value) Value {
|
|
return Add(x, y.Neg())
|
|
}
|
|
|
|
func (x Value) Sub(y Value) Value {
|
|
return Sub(x, y)
|
|
}
|
|
|
|
// Add returns the sum of two Value's
|
|
func Add(x, y Value) Value {
|
|
switch {
|
|
case x.sign == signZero:
|
|
return y
|
|
case y.sign == signZero:
|
|
return x
|
|
case x.IsInf():
|
|
if y.sign == -x.sign {
|
|
return Zero
|
|
}
|
|
return x
|
|
case y.IsInf():
|
|
return y
|
|
}
|
|
if !align(&x, &y) {
|
|
return x
|
|
}
|
|
if x.sign != y.sign {
|
|
return usub(x, y)
|
|
}
|
|
return uadd(x, y)
|
|
}
|
|
|
|
func (x Value) Add(y Value) Value {
|
|
return Add(x, y)
|
|
}
|
|
|
|
func uadd(x, y Value) Value {
|
|
return New(x.sign, x.coef+y.coef, int(x.exp))
|
|
}
|
|
|
|
func usub(x, y Value) Value {
|
|
if x.coef < y.coef {
|
|
return New(-x.sign, y.coef-x.coef, int(x.exp))
|
|
}
|
|
return New(x.sign, x.coef-y.coef, int(x.exp))
|
|
}
|
|
|
|
func align(x, y *Value) bool {
|
|
if x.exp == y.exp {
|
|
return true
|
|
}
|
|
if x.exp < y.exp {
|
|
*x, *y = *y, *x // swap
|
|
}
|
|
yshift := ilog10(y.coef)
|
|
e := int(x.exp - y.exp)
|
|
if e > yshift {
|
|
return false
|
|
}
|
|
yshift = e
|
|
// check(0 <= yshift && yshift <= 20)
|
|
//y.coef = (y.coef + halfpow10[yshift]) / pow10[yshift]
|
|
y.coef = (y.coef) / pow10[yshift]
|
|
// check(int(y.exp)+yshift == int(x.exp))
|
|
return true
|
|
}
|
|
|
|
const e7 = 10000000
|
|
|
|
// Mul returns the product of two Value's
|
|
func Mul(x, y Value) Value {
|
|
sign := x.sign * y.sign
|
|
switch {
|
|
case sign == signZero:
|
|
return Zero
|
|
case x.IsInf() || y.IsInf():
|
|
return Inf(sign)
|
|
}
|
|
e := int(x.exp) + int(y.exp)
|
|
|
|
// split unevenly to use full 64 bit range to get more precision
|
|
// and avoid needing xlo * ylo
|
|
xhi := x.coef / e7 // 9 digits
|
|
xlo := x.coef % e7 // 7 digits
|
|
yhi := y.coef / e7 // 9 digits
|
|
ylo := y.coef % e7 // 7 digits
|
|
|
|
c := xhi * yhi
|
|
if (xlo | ylo) != 0 {
|
|
c += (xlo*yhi + ylo*xhi) / e7
|
|
}
|
|
return New(sign, c, e-2)
|
|
}
|
|
|
|
func (x Value) Mul(y Value) Value {
|
|
return Mul(x, y)
|
|
}
|
|
|
|
// Div returns the quotient of two Value's
|
|
func Div(x, y Value) Value {
|
|
sign := x.sign * y.sign
|
|
switch {
|
|
case x.sign == signZero:
|
|
return x
|
|
case y.sign == signZero:
|
|
return Inf(x.sign)
|
|
case x.IsInf():
|
|
if y.IsInf() {
|
|
if sign < 0 {
|
|
return NegOne
|
|
}
|
|
return One
|
|
}
|
|
return Inf(sign)
|
|
case y.IsInf():
|
|
return Zero
|
|
}
|
|
coef := div128(x.coef, y.coef)
|
|
return New(sign, coef, int(x.exp)-int(y.exp))
|
|
}
|
|
|
|
func (x Value) Div(y Value) Value {
|
|
return Div(x, y)
|
|
}
|
|
|
|
// Hash returns a hash value for a Value
|
|
func (dn Value) Hash() uint32 {
|
|
return uint32(dn.coef>>32) ^ uint32(dn.coef) ^
|
|
uint32(dn.sign)<<16 ^ uint32(dn.exp)<<8
|
|
}
|
|
|
|
// Format converts a number to a string with a specified format
|
|
func (dn Value) Format(mask string) string {
|
|
if dn.IsInf() {
|
|
return "#"
|
|
}
|
|
n := dn
|
|
before := 0
|
|
after := 0
|
|
intpart := true
|
|
for _, mc := range mask {
|
|
switch mc {
|
|
case '.':
|
|
intpart = false
|
|
case '#':
|
|
if intpart {
|
|
before++
|
|
} else {
|
|
after++
|
|
}
|
|
}
|
|
}
|
|
if before+after == 0 || n.Exp() > before {
|
|
return "#" // too big to fit in mask
|
|
}
|
|
n = n.Round(after, HalfUp)
|
|
e := n.Exp()
|
|
var digits []byte
|
|
if n.IsZero() && after == 0 {
|
|
digits = []byte("0")
|
|
e = 1
|
|
} else {
|
|
digits = strconv.AppendUint(make([]byte, 0, digitsMax), n.Coef(), 10)
|
|
digits = bytes.TrimRight(digits, "0")
|
|
}
|
|
nd := len(digits)
|
|
|
|
di := e - before
|
|
// check(di <= 0)
|
|
var buf strings.Builder
|
|
sign := n.Sign()
|
|
signok := (sign >= 0)
|
|
frac := false
|
|
for _, mc := range []byte(mask) {
|
|
switch mc {
|
|
case '#':
|
|
if 0 <= di && di < nd {
|
|
buf.WriteByte(digits[di])
|
|
} else if frac || di >= 0 {
|
|
buf.WriteByte('0')
|
|
}
|
|
di++
|
|
case ',':
|
|
if di > 0 {
|
|
buf.WriteByte(',')
|
|
}
|
|
case '-', '(':
|
|
signok = true
|
|
if sign < 0 {
|
|
buf.WriteByte(mc)
|
|
}
|
|
case ')':
|
|
if sign < 0 {
|
|
buf.WriteByte(mc)
|
|
} else {
|
|
buf.WriteByte(' ')
|
|
}
|
|
case '.':
|
|
frac = true
|
|
fallthrough
|
|
default:
|
|
buf.WriteByte(mc)
|
|
}
|
|
}
|
|
if !signok {
|
|
return "-" // negative not handled by mask
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func Clamp(x, min, max Value) Value {
|
|
if x.Compare(min) < 0 {
|
|
return min
|
|
}
|
|
if x.Compare(max) > 0 {
|
|
return max
|
|
}
|
|
return x
|
|
}
|
|
|
|
func (x Value) Clamp(min, max Value) Value {
|
|
if x.Compare(min) < 0 {
|
|
return min
|
|
}
|
|
if x.Compare(max) > 0 {
|
|
return max
|
|
}
|
|
return x
|
|
} |