bbgo_origin/pkg/fixedpoint/convert.go

349 lines
5.9 KiB
Go

package fixedpoint
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"strconv"
"sync/atomic"
)
const MaxPrecision = 12
const DefaultPrecision = 8
const DefaultPow = 1e8
type Value int64
func (v Value) Value() (driver.Value, error) {
return v.Float64(), nil
}
func (v *Value) Scan(src interface{}) error {
switch d := src.(type) {
case int64:
*v = Value(d)
return nil
case float64:
*v = NewFromFloat(d)
return nil
case []byte:
vv, err := NewFromString(string(d))
if err != nil {
return err
}
*v = vv
return nil
default:
}
return fmt.Errorf("fixedpoint.Value scan error, type: %T is not supported, value; %+v", src, src)
}
func (v Value) Float64() float64 {
return float64(v) / DefaultPow
}
func (v Value) String() string {
return strconv.FormatFloat(float64(v)/DefaultPow, 'f', -1, 64)
}
func (v Value) Percentage() string {
return fmt.Sprintf("%.2f%%", v.Float64() * 100.0)
}
func (v Value) SignedPercentage() string {
if v > 0 {
return "+" + v.Percentage()
}
return v.Percentage()
}
func (v Value) Int64() int64 {
return int64(v.Float64())
}
func (v Value) Int() int {
return int(v.Float64())
}
// BigMul is the math/big version multiplication
func (v Value) BigMul(v2 Value) Value {
x := new(big.Int).Mul(big.NewInt(int64(v)), big.NewInt(int64(v2)))
return Value(x.Int64() / DefaultPow)
}
func (v Value) Mul(v2 Value) Value {
return NewFromFloat(v.Float64() * v2.Float64())
}
func (v Value) MulInt(v2 int) Value {
return NewFromFloat(v.Float64() * float64(v2))
}
func (v Value) MulFloat64(v2 float64) Value {
return NewFromFloat(v.Float64() * v2)
}
func (v Value) Div(v2 Value) Value {
return NewFromFloat(v.Float64() / v2.Float64())
}
func (v Value) DivFloat64(v2 float64) Value {
return NewFromFloat(v.Float64() / v2)
}
func (v Value) Floor() Value {
return NewFromFloat(math.Floor(v.Float64()))
}
func (v Value) Ceil() Value {
return NewFromFloat(math.Ceil(v.Float64()))
}
func (v Value) Sub(v2 Value) Value {
return Value(int64(v) - int64(v2))
}
func (v Value) Add(v2 Value) Value {
return Value(int64(v) + int64(v2))
}
func (v *Value) AtomicAdd(v2 Value) {
atomic.AddInt64((*int64)(v), int64(v2))
}
func (v *Value) AtomicLoad() Value {
i := atomic.LoadInt64((*int64)(v))
return Value(i)
}
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 = NewFromInt64(i)
return
}
var s string
if err = unmarshal(&s); err == nil {
nv, err2 := NewFromString(s)
if err2 == nil {
*v = nv
return
}
}
return err
}
func (v Value) MarshalJSON() ([]byte, error) {
f := float64(v) / DefaultPow
o := strconv.FormatFloat(f, 'f', 8, 64)
return []byte(o), nil
}
func (v *Value) UnmarshalJSON(data []byte) error {
var a interface{}
var err = json.Unmarshal(data, &a)
if err != nil {
return err
}
switch d := a.(type) {
case float64:
*v = NewFromFloat(d)
case float32:
*v = NewFromFloat32(d)
case int:
*v = NewFromInt(d)
case int64:
*v = NewFromInt64(d)
case string:
v2, err := NewFromString(d)
if err != nil {
return err
}
*v = v2
default:
return fmt.Errorf("unsupported type: %T %v", d, d)
}
return nil
}
func Must(v Value, err error) Value {
if err != nil {
panic(err)
}
return v
}
var ErrPrecisionLoss = errors.New("precision loss")
func Parse(input string) (num int64, numDecimalPoints int, err error) {
length := len(input)
isPercentage := input[length-1] == '%'
if isPercentage {
length -= 1
input = input[0:length]
}
var neg int64 = 1
var digit int64
for i := 0; i < length; i++ {
c := input[i]
if c == '-' {
neg = -1
} else if c >= '0' && c <= '9' {
digit, err = strconv.ParseInt(string(c), 10, 64)
if err != nil {
return
}
num = num*10 + digit
} else if c == '.' {
i++
if i > len(input)-1 {
err = fmt.Errorf("expect fraction numbers after dot")
return
}
for j := i; j < len(input); j++ {
fc := input[j]
if fc >= '0' && fc <= '9' {
digit, err = strconv.ParseInt(string(fc), 10, 64)
if err != nil {
return
}
numDecimalPoints++
num = num*10 + digit
if numDecimalPoints >= MaxPrecision {
return num, numDecimalPoints, ErrPrecisionLoss
}
} else {
err = fmt.Errorf("expect digit, got %c", fc)
return
}
}
break
} else {
err = fmt.Errorf("unexpected char %c", c)
return
}
}
num = num * neg
if isPercentage {
numDecimalPoints += 2
}
return num, numDecimalPoints, nil
}
func NewFromString(input string) (Value, error) {
length := len(input)
if length == 0 {
return 0, nil
}
isPercentage := input[length-1] == '%'
if isPercentage {
input = input[0 : length-1]
}
v, err := strconv.ParseFloat(input, 64)
if err != nil {
return 0, err
}
if isPercentage {
v = v * 0.01
}
return NewFromFloat(v), nil
}
func MustNewFromString(input string) Value {
v, err := NewFromString(input)
if err != nil {
panic(fmt.Errorf("can not parse %s into fixedpoint, error: %s", input, err.Error()))
}
return v
}
func NewFromFloat(val float64) Value {
return Value(int64(math.Round(val * DefaultPow)))
}
func NewFromFloat32(val float32) Value {
return Value(int64(math.Round(float64(val) * DefaultPow)))
}
func NewFromInt(val int) Value {
return Value(int64(val * DefaultPow))
}
func NewFromInt64(val int64) Value {
return Value(val * DefaultPow)
}
func NumFractionalDigits(a Value) int {
numPow := 0
for pow := int64(DefaultPow); pow%10 != 1; pow /= 10 {
numPow++
}
numZeros := 0
for v := int64(a); v%10 == 0; v /= 10 {
numZeros++
}
return numPow - numZeros
}
func Min(a, b Value) Value {
if a < b {
return a
}
return b
}
func Max(a, b Value) Value {
if a > b {
return a
}
return b
}
func Abs(a Value) Value {
if a < 0 {
return -a
}
return a
}