mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 16:55: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"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -111,7 +112,9 @@ func (v Value) FormatPercentage(prec int) string {
|
||||||
if v == 0 {
|
if v == 0 {
|
||||||
return "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 + "%"
|
return result + "%"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,9 +223,7 @@ func (v *Value) UnmarshalYAML(unmarshal func(a interface{}) error) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Value) MarshalJSON() ([]byte, error) {
|
func (v Value) MarshalJSON() ([]byte, error) {
|
||||||
f := float64(v) / DefaultPow
|
return []byte(v.FormatString(DefaultPrecision)), nil
|
||||||
o := strconv.FormatFloat(f, 'f', 8, 64)
|
|
||||||
return []byte(o), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) UnmarshalJSON(data []byte) error {
|
func (v *Value) UnmarshalJSON(data []byte) error {
|
||||||
|
@ -319,17 +320,72 @@ func NewFromString(input string) (Value, error) {
|
||||||
if isPercentage {
|
if isPercentage {
|
||||||
input = input[0 : length-1]
|
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)
|
} else if c == '.' {
|
||||||
if err != nil {
|
dotIndex = i
|
||||||
return 0, err
|
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 {
|
func MustNewFromString(input string) Value {
|
||||||
|
@ -360,7 +416,7 @@ func Must(v Value, err error) Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFromFloat(val float64) 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 {
|
func NewFromInt(val int64) Value {
|
||||||
|
|
|
@ -29,6 +29,8 @@ const (
|
||||||
coefMax = 9999_9999_9999_9999
|
coefMax = 9999_9999_9999_9999
|
||||||
digitsMax = 16
|
digitsMax = 16
|
||||||
shiftMax = digitsMax - 1
|
shiftMax = digitsMax - 1
|
||||||
|
// to switch between scientific notion and normal presentation format
|
||||||
|
maxLeadingZeros = 19
|
||||||
)
|
)
|
||||||
|
|
||||||
// common values
|
// common values
|
||||||
|
@ -251,9 +253,12 @@ func Inf(sign int8) Value {
|
||||||
|
|
||||||
func (dn Value) FormatString(prec int) string {
|
func (dn Value) FormatString(prec int) string {
|
||||||
if dn.sign == 0 {
|
if dn.sign == 0 {
|
||||||
return "0"
|
if prec <= 0 {
|
||||||
|
return "0"
|
||||||
|
} else {
|
||||||
|
return "0." + strings.Repeat("0", prec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const maxLeadingZeros = 7
|
|
||||||
sign := ""
|
sign := ""
|
||||||
if dn.sign < 0 {
|
if dn.sign < 0 {
|
||||||
sign = "-"
|
sign = "-"
|
||||||
|
@ -266,11 +271,18 @@ func (dn Value) FormatString(prec int) string {
|
||||||
e := int(dn.exp) - nd
|
e := int(dn.exp) - nd
|
||||||
if -maxLeadingZeros <= dn.exp && dn.exp <= 0 {
|
if -maxLeadingZeros <= dn.exp && dn.exp <= 0 {
|
||||||
// decimal to the left
|
// 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 {
|
} else if -nd < e && e <= -1 {
|
||||||
// decimal within
|
// decimal within
|
||||||
dec := nd + e
|
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 {
|
} else if 0 < dn.exp && dn.exp <= digitsMax {
|
||||||
// decimal to the right
|
// decimal to the right
|
||||||
if prec > 0 {
|
if prec > 0 {
|
||||||
|
@ -282,7 +294,7 @@ func (dn Value) FormatString(prec int) string {
|
||||||
// scientific notation
|
// scientific notation
|
||||||
after := ""
|
after := ""
|
||||||
if nd > 1 {
|
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))
|
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 {
|
if dn.sign == 0 {
|
||||||
return "0"
|
return "0"
|
||||||
}
|
}
|
||||||
const maxLeadingZeros = 7
|
|
||||||
sign := ""
|
sign := ""
|
||||||
if dn.sign < 0 {
|
if dn.sign < 0 {
|
||||||
sign = "-"
|
sign = "-"
|
||||||
|
@ -328,7 +339,6 @@ func (dn Value) Percentage() string {
|
||||||
if dn.sign == 0 {
|
if dn.sign == 0 {
|
||||||
return "0%"
|
return "0%"
|
||||||
}
|
}
|
||||||
const maxLeadingZeros = 7
|
|
||||||
sign := ""
|
sign := ""
|
||||||
if dn.sign < 0 {
|
if dn.sign < 0 {
|
||||||
sign = "-"
|
sign = "-"
|
||||||
|
@ -362,9 +372,12 @@ func (dn Value) Percentage() string {
|
||||||
|
|
||||||
func (dn Value) FormatPercentage(prec int) string {
|
func (dn Value) FormatPercentage(prec int) string {
|
||||||
if dn.sign == 0 {
|
if dn.sign == 0 {
|
||||||
return "0"
|
if prec <= 0 {
|
||||||
|
return "0"
|
||||||
|
} else {
|
||||||
|
return "0." + strings.Repeat("0", prec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const maxLeadingZeros = 7
|
|
||||||
sign := ""
|
sign := ""
|
||||||
if dn.sign < 0 {
|
if dn.sign < 0 {
|
||||||
sign = "-"
|
sign = "-"
|
||||||
|
@ -379,19 +392,30 @@ func (dn Value) FormatPercentage(prec int) string {
|
||||||
|
|
||||||
if -maxLeadingZeros <= exp && exp <= 0 {
|
if -maxLeadingZeros <= exp && exp <= 0 {
|
||||||
// decimal to the left
|
// 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 {
|
} else if -nd < e && e <= -1 {
|
||||||
// decimal within
|
// decimal within
|
||||||
dec := nd + e
|
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 {
|
} else if 0 < exp && exp <= digitsMax {
|
||||||
// decimal to the right
|
// 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 {
|
} else {
|
||||||
// scientific notation
|
// scientific notation
|
||||||
after := ""
|
after := ""
|
||||||
if nd > 1 {
|
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)) + "%"
|
return sign + digits[:1] + after + "e" + strconv.Itoa(int(exp-1)) + "%"
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,14 @@ func TestInternal(t *testing.T) {
|
||||||
f = MustNewFromString("1.00000000000000111")
|
f = MustNewFromString("1.00000000000000111")
|
||||||
assert.Equal(t, "1.000000000000001", f.String())
|
assert.Equal(t, "1.000000000000001", f.String())
|
||||||
f = MustNewFromString("1.1e-15")
|
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())
|
assert.Equal(t, 16, f.NumFractionalDigits())
|
||||||
f = MustNewFromString("1.00000000000000111")
|
f = MustNewFromString("1.00000000000000111")
|
||||||
assert.Equal(t, "1.000000000000001", f.String())
|
assert.Equal(t, "1.000000000000001", f.String())
|
||||||
f = MustNewFromString("0.0000000001000111")
|
f = MustNewFromString("0.00000000000000000001000111")
|
||||||
assert.Equal(t, "1.000111e-10", f.String())
|
assert.Equal(t, "0.00000000000000000001000111", f.String())
|
||||||
|
f = MustNewFromString("0.000000000000000000001000111")
|
||||||
|
assert.Equal(t, "1.000111e-21", f.String())
|
||||||
f = MustNewFromString("1e-100")
|
f = MustNewFromString("1e-100")
|
||||||
assert.Equal(t, 100, f.NumFractionalDigits())
|
assert.Equal(t, 100, f.NumFractionalDigits())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package fixedpoint
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Delta = 1e-9
|
const Delta = 1e-9
|
||||||
|
@ -128,6 +128,46 @@ func TestFromString(t *testing.T) {
|
||||||
assert.Equal(t, Zero, f)
|
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) {
|
func TestNumFractionalDigits(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
Loading…
Reference in New Issue
Block a user