mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 23:05:15 +00:00
Merge pull request #458 from zenixls2/fix/fixedpoint
fix: dnum panic, precision loss in parsing string in legacy
This commit is contained in:
commit
f90070771d
|
@ -9,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
|
@ -111,7 +112,9 @@ func (v Value) FormatPercentage(prec int) string {
|
|||
if v == 0 {
|
||||
return "0"
|
||||
}
|
||||
result := strconv.FormatFloat(float64(v)/DefaultPow*100., 'f', prec, 64)
|
||||
pow := math.Pow10(prec)
|
||||
result := strconv.FormatFloat(
|
||||
math.Trunc(float64(v)/DefaultPow * pow * 100.) / pow, 'f', prec, 64)
|
||||
return result + "%"
|
||||
}
|
||||
|
||||
|
@ -220,9 +223,7 @@ func (v *Value) UnmarshalYAML(unmarshal func(a interface{}) error) (err error) {
|
|||
}
|
||||
|
||||
func (v Value) MarshalJSON() ([]byte, error) {
|
||||
f := float64(v) / DefaultPow
|
||||
o := strconv.FormatFloat(f, 'f', 8, 64)
|
||||
return []byte(o), nil
|
||||
return []byte(v.FormatString(DefaultPrecision)), nil
|
||||
}
|
||||
|
||||
func (v *Value) UnmarshalJSON(data []byte) error {
|
||||
|
@ -319,17 +320,72 @@ func NewFromString(input string) (Value, error) {
|
|||
if isPercentage {
|
||||
input = input[0 : length-1]
|
||||
}
|
||||
dotIndex := -1
|
||||
hasDecimal := false
|
||||
decimalCount := 0
|
||||
// if is decimal, we don't need this
|
||||
hasScientificNotion := false
|
||||
scIndex := -1
|
||||
for i, c := range(input) {
|
||||
if hasDecimal {
|
||||
if c <= '9' && c >= '0' {
|
||||
decimalCount++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
v, err := strconv.ParseFloat(input, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if c == '.' {
|
||||
dotIndex = i
|
||||
hasDecimal = true
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
hasScientificNotion = true
|
||||
scIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasDecimal {
|
||||
after := input[dotIndex+1:len(input)]
|
||||
if decimalCount >= 8 {
|
||||
after = after[0:8] + "." + after[8:len(after)]
|
||||
} else {
|
||||
after = after[0:decimalCount] + strings.Repeat("0", 8-decimalCount) + after[decimalCount:len(after)]
|
||||
}
|
||||
input = input[0:dotIndex] + after
|
||||
v, err := strconv.ParseFloat(input, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if isPercentage {
|
||||
v = v * 0.01
|
||||
}
|
||||
|
||||
return Value(int64(math.Trunc(v))), nil
|
||||
|
||||
} else if hasScientificNotion {
|
||||
exp, err := strconv.ParseInt(input[scIndex+1:len(input)], 10, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v, err := strconv.ParseFloat(input[0:scIndex+1] + strconv.FormatInt(exp + 8, 10), 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return Value(int64(math.Trunc(v))), nil
|
||||
} else {
|
||||
v, err := strconv.ParseInt(input, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if isPercentage {
|
||||
v = v * DefaultPow / 100
|
||||
} else {
|
||||
v = v * DefaultPow
|
||||
}
|
||||
return Value(v), nil
|
||||
}
|
||||
|
||||
if isPercentage {
|
||||
v = v * 0.01
|
||||
}
|
||||
|
||||
return NewFromFloat(v), nil
|
||||
}
|
||||
|
||||
func MustNewFromString(input string) Value {
|
||||
|
@ -360,7 +416,7 @@ func Must(v Value, err error) Value {
|
|||
}
|
||||
|
||||
func NewFromFloat(val float64) Value {
|
||||
return Value(int64(math.Round(val * DefaultPow)))
|
||||
return Value(int64(math.Trunc(val * DefaultPow)))
|
||||
}
|
||||
|
||||
func NewFromInt(val int64) Value {
|
||||
|
|
|
@ -29,6 +29,8 @@ const (
|
|||
coefMax = 9999_9999_9999_9999
|
||||
digitsMax = 16
|
||||
shiftMax = digitsMax - 1
|
||||
// to switch between scientific notion and normal presentation format
|
||||
maxLeadingZeros = 19
|
||||
)
|
||||
|
||||
// common values
|
||||
|
@ -251,9 +253,12 @@ func Inf(sign int8) Value {
|
|||
|
||||
func (dn Value) FormatString(prec int) string {
|
||||
if dn.sign == 0 {
|
||||
return "0"
|
||||
if prec <= 0 {
|
||||
return "0"
|
||||
} else {
|
||||
return "0." + strings.Repeat("0", prec)
|
||||
}
|
||||
}
|
||||
const maxLeadingZeros = 7
|
||||
sign := ""
|
||||
if dn.sign < 0 {
|
||||
sign = "-"
|
||||
|
@ -266,11 +271,18 @@ func (dn Value) FormatString(prec int) string {
|
|||
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[:min(prec, nd)] + strings.Repeat("0", max(0, prec-nd+e+nd))
|
||||
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
|
||||
return sign + digits[:dec] + "." + digits[dec:min(dec+prec, nd)] + strings.Repeat("0", max(0, min(dec+prec, nd)-dec-prec))
|
||||
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 {
|
||||
|
@ -282,7 +294,7 @@ func (dn Value) FormatString(prec int) string {
|
|||
// scientific notation
|
||||
after := ""
|
||||
if nd > 1 {
|
||||
after = "." + digits[1:min(1+prec, nd)] + strings.Repeat("0", min(1+prec, nd)-1-prec)
|
||||
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))
|
||||
}
|
||||
|
@ -293,7 +305,6 @@ func (dn Value) String() string {
|
|||
if dn.sign == 0 {
|
||||
return "0"
|
||||
}
|
||||
const maxLeadingZeros = 7
|
||||
sign := ""
|
||||
if dn.sign < 0 {
|
||||
sign = "-"
|
||||
|
@ -328,7 +339,6 @@ func (dn Value) Percentage() string {
|
|||
if dn.sign == 0 {
|
||||
return "0%"
|
||||
}
|
||||
const maxLeadingZeros = 7
|
||||
sign := ""
|
||||
if dn.sign < 0 {
|
||||
sign = "-"
|
||||
|
@ -362,9 +372,12 @@ func (dn Value) Percentage() string {
|
|||
|
||||
func (dn Value) FormatPercentage(prec int) string {
|
||||
if dn.sign == 0 {
|
||||
return "0"
|
||||
if prec <= 0 {
|
||||
return "0"
|
||||
} else {
|
||||
return "0." + strings.Repeat("0", prec)
|
||||
}
|
||||
}
|
||||
const maxLeadingZeros = 7
|
||||
sign := ""
|
||||
if dn.sign < 0 {
|
||||
sign = "-"
|
||||
|
@ -379,19 +392,30 @@ func (dn Value) FormatPercentage(prec int) string {
|
|||
|
||||
if -maxLeadingZeros <= exp && exp <= 0 {
|
||||
// decimal to the left
|
||||
return sign + "0." + strings.Repeat("0", -e-nd) + digits[:min(prec, nd)] + strings.Repeat("0", max(0, prec-nd+e+nd)) + "%"
|
||||
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
|
||||
return sign + digits[:dec] + "." + digits[dec:min(dec+prec, nd)] + strings.Repeat("0", max(0, min(dec+prec, nd)-dec-prec)) + "%"
|
||||
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
|
||||
return sign + digits + strings.Repeat("0", e) + "." + strings.Repeat("0", prec) + "%"
|
||||
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", min(1+prec, nd)-1-prec)
|
||||
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)) + "%"
|
||||
}
|
||||
|
|
|
@ -24,12 +24,14 @@ func TestInternal(t *testing.T) {
|
|||
f = MustNewFromString("1.00000000000000111")
|
||||
assert.Equal(t, "1.000000000000001", f.String())
|
||||
f = MustNewFromString("1.1e-15")
|
||||
assert.Equal(t, "1.1e-15", f.String())
|
||||
assert.Equal(t, "0.0000000000000011", f.String())
|
||||
assert.Equal(t, 16, f.NumFractionalDigits())
|
||||
f = MustNewFromString("1.00000000000000111")
|
||||
assert.Equal(t, "1.000000000000001", f.String())
|
||||
f = MustNewFromString("0.0000000001000111")
|
||||
assert.Equal(t, "1.000111e-10", f.String())
|
||||
f = MustNewFromString("0.00000000000000000001000111")
|
||||
assert.Equal(t, "0.00000000000000000001000111", f.String())
|
||||
f = MustNewFromString("0.000000000000000000001000111")
|
||||
assert.Equal(t, "1.000111e-21", f.String())
|
||||
f = MustNewFromString("1e-100")
|
||||
assert.Equal(t, 100, f.NumFractionalDigits())
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package fixedpoint
|
|||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const Delta = 1e-9
|
||||
|
@ -128,6 +128,46 @@ func TestFromString(t *testing.T) {
|
|||
assert.Equal(t, Zero, f)
|
||||
}
|
||||
|
||||
func TestJson(t *testing.T) {
|
||||
p := MustNewFromString("0")
|
||||
e, err := json.Marshal(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0.00000000", string(e))
|
||||
p = MustNewFromString("1.00000003")
|
||||
e, err = json.Marshal(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1.00000003", string(e))
|
||||
p = MustNewFromString("1.000000003")
|
||||
e, err = json.Marshal(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1.00000000", string(e))
|
||||
p = MustNewFromString("1.000000008")
|
||||
e, err = json.Marshal(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1.00000000", string(e))
|
||||
p = MustNewFromString("0.999999999")
|
||||
e, err = json.Marshal(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0.99999999", string(e))
|
||||
|
||||
p = MustNewFromString("1.2e-9")
|
||||
e, err = json.Marshal(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0.00000000", p.FormatString(8))
|
||||
assert.Equal(t, "0.00000000", string(e))
|
||||
|
||||
|
||||
_ = json.Unmarshal([]byte("0.00153917575"), &p)
|
||||
assert.Equal(t, "0.00153917", p.FormatString(8))
|
||||
|
||||
var q Value
|
||||
q = NewFromFloat(0.00153917575)
|
||||
assert.Equal(t, p, q)
|
||||
_ = json.Unmarshal([]byte("6e-8"), &p)
|
||||
_ = json.Unmarshal([]byte("0.000062"), &q)
|
||||
assert.Equal(t, "0.00006194", q.Sub(p).String())
|
||||
}
|
||||
|
||||
func TestNumFractionalDigits(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
Loading…
Reference in New Issue
Block a user