bbgo_origin/pkg/fixedpoint/convert.go

448 lines
7.5 KiB
Go

//go:build !dnum
package fixedpoint
import (
"bytes"
"database/sql/driver"
"errors"
"fmt"
"math"
"strconv"
"sync/atomic"
)
const MaxPrecision = 12
const DefaultPrecision = 8
const DefaultPow = 1e8
type Value int64
const Zero = Value(0)
const One = Value(1e8)
const NegOne = Value(-1e8)
type RoundingMode int
const (
Up RoundingMode = iota
Down
HalfUp
)
// Trunc returns the integer portion (truncating any fractional part)
func (v Value) Trunc() Value {
return NewFromFloat(math.Floor(v.Float64()))
}
func (v Value) Round(r int, mode RoundingMode) Value {
pow := math.Pow10(r)
result := v.Float64() * pow
switch mode {
case Up:
return NewFromFloat(math.Ceil(result) / pow)
case HalfUp:
return NewFromFloat(math.Floor(result+0.5) / pow)
case Down:
return NewFromFloat(math.Floor(result) / pow)
}
return v
}
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 = NewFromInt(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) FormatString(prec int) string {
result := strconv.FormatFloat(float64(v)/DefaultPow, 'f', prec+1, 64)
return result[:len(result)-1]
}
func (v Value) Percentage() string {
if v == 0 {
return "0"
}
return strconv.FormatFloat(float64(v)/DefaultPow*100., 'f', -1, 64) + "%"
}
func (v Value) FormatPercentage(prec int) string {
if v == 0 {
return "0"
}
result := strconv.FormatFloat(float64(v)/DefaultPow*100., 'f', prec+1, 64)
return result[:len(result)-1] + "%"
}
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 {
n := v.Int64()
if int64(int(n)) != n {
panic("unable to convert Value to int32")
}
return int(n)
}
func (v Value) Neg() Value {
return -v
}
// TODO inf
func (v Value) Sign() int {
if v > 0 {
return 1
} else if v == 0 {
return 0
} else {
return -1
}
}
func (v Value) IsZero() bool {
return v == 0
}
func Mul(x, y Value) Value {
return NewFromFloat(x.Float64() * y.Float64())
}
func (v Value) Mul(v2 Value) Value {
return NewFromFloat(v.Float64() * v2.Float64())
}
func Div(x, y Value) Value {
return NewFromFloat(x.Float64() / y.Float64())
}
func (v Value) Div(v2 Value) Value {
return NewFromFloat(v.Float64() / v2.Float64())
}
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 = NewFromInt(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 {
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 = NewFromString(string(data)); err != nil {
return err
}
return nil
}
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 NewFromBytes(input []byte) (Value, error) {
return NewFromString(string(input))
}
func MustNewFromBytes(input []byte) (v Value) {
var err error
if v, err = NewFromString(string(input)); err != nil {
return Zero
}
return v
}
func Must(v Value, err error) Value {
if err != nil {
panic(err)
}
return v
}
func NewFromFloat(val float64) Value {
return Value(int64(math.Round(val * DefaultPow)))
}
func NewFromInt(val int64) Value {
return Value(val * DefaultPow)
}
func (a Value) MulExp(exp int) Value {
return Value(int64(float64(a) * math.Pow(10, float64(exp))))
}
func (a Value) NumIntDigits() int {
digits := 0
target := int64(a)
for pow := int64(DefaultPow); pow <= target; pow *= 10 {
digits++
}
return digits
}
// TODO: speedup
func (a Value) NumFractionalDigits() int {
if a == 0 {
return 0
}
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 Compare(x, y Value) int {
if x > y {
return 1
} else if x == y {
return 0
} else {
return -1
}
}
func (x Value) Compare(y Value) int {
if x > y {
return 1
} else if x == y {
return 0
} else {
return -1
}
}
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 Equal(x, y Value) bool {
return x == y
}
func (x Value) Eq(y Value) bool {
return x == y
}
func Abs(a Value) Value {
if a < 0 {
return -a
}
return a
}