mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-23 23:35:14 +00:00
370 lines
6.2 KiB
Go
370 lines
6.2 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) Abs() Value {
|
|
if v < 0 {
|
|
return -v
|
|
}
|
|
return v
|
|
}
|
|
|
|
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 NewFromAny(any interface{}) (Value, error) {
|
|
switch v := any.(type) {
|
|
case string:
|
|
return NewFromString(v)
|
|
case float64:
|
|
return NewFromFloat(v), nil
|
|
case int64:
|
|
return NewFromInt64(v), nil
|
|
|
|
default:
|
|
return 0, fmt.Errorf("fixedpoint unsupported type %v", v)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|