add back legacy implementation

This commit is contained in:
zenix 2022-02-15 11:56:09 +09:00
parent cdba7924b4
commit eb70410f80
7 changed files with 197 additions and 140 deletions

View File

@ -44,3 +44,6 @@ jobs:
- name: Test
run: go test -v ./pkg/...
- name: TestDnum
run: go test -tag dnum -v ./pkg/...

View File

@ -1,3 +1,5 @@
//go:build !dnum
package fixedpoint
import (
@ -6,7 +8,6 @@ import (
"errors"
"fmt"
"math"
"math/big"
"strconv"
"sync/atomic"
)
@ -18,6 +19,37 @@ 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
}
@ -62,8 +94,22 @@ func (v Value) String() string {
return strconv.FormatFloat(float64(v)/DefaultPow, 'f', -1, 64)
}
func (v Value) FormatString(prec int) string {
return strconv.FormatFloat(float64(v)/DefaultPow, 'f', prec, 64)
}
func (v Value) Percentage() string {
return fmt.Sprintf("%.2f%%", v.Float64()*100.0)
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"
}
return strconv.FormatFloat(float64(v)/DefaultPow*100., 'f', prec, 64) + "%"
}
func (v Value) SignedPercentage() string {
@ -78,35 +124,48 @@ func (v Value) Int64() int64 {
}
func (v Value) Int() int {
return int(v.Float64())
n := v.Int64()
if int64(int(n)) != n {
panic("unable to convert Value to int32")
}
return int(n)
}
// 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) 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 (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 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) DivFloat64(v2 float64) Value {
return NewFromFloat(v.Float64() / v2)
}
func (v Value) Floor() Value {
return NewFromFloat(math.Floor(v.Float64()))
}
@ -141,7 +200,7 @@ func (v *Value) UnmarshalYAML(unmarshal func(a interface{}) error) (err error) {
var i int64
if err = unmarshal(&i); err == nil {
*v = NewFromInt64(i)
*v = NewFromInt(i)
return
}
@ -173,15 +232,13 @@ func (v *Value) UnmarshalJSON(data []byte) error {
switch d := a.(type) {
case float64:
*v = NewFromFloat(d)
case float32:
*v = NewFromFloat32(d)
case int:
*v = NewFromInt(d)
*v = NewFromFloat(float64(d))
case int64:
*v = NewFromInt64(d)
*v = NewFromInt(d)
case int:
*v = NewFromInt(int64(d))
case string:
v2, err := NewFromString(d)
@ -199,14 +256,6 @@ func (v *Value) UnmarshalJSON(data []byte) error {
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) {
@ -271,20 +320,6 @@ func Parse(input string) (num int64, numDecimalPoints int, err error) {
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)
@ -317,23 +352,48 @@ func MustNewFromString(input string) Value {
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 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 {
func NewFromInt(val int64) Value {
return Value(val * DefaultPow)
}
func NumFractionalDigits(a Value) int {
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 {
numPow := 0
for pow := int64(DefaultPow); pow%10 != 1; pow /= 10 {
numPow++
@ -345,6 +405,26 @@ func NumFractionalDigits(a Value) int {
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
@ -361,6 +441,14 @@ func Max(a, b Value) Value {
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

View File

@ -1,3 +1,5 @@
//go:build dnum
package fixedpoint
import (
@ -1026,8 +1028,7 @@ func Must(v Value, err error) Value {
// v * 10^(exp)
func (v Value) MulExp(exp int) Value {
v.exp += exp
return v
return Value{v.coef, v.sign, v.exp + exp}
}
// Sub returns the difference of two Value's

View File

@ -0,0 +1,14 @@
//go:build dnum
package fixedpoint
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestDelta(t *testing.T) {
f1 := MustNewFromString("0.0009763593380614657")
f2 := NewFromInt(42300)
assert.InDelta(t, f1.Mul(f2).Float64(), 41.3, 1e-14)
}

View File

@ -0,0 +1,28 @@
//go:build !dnum
package fixedpoint
import (
"testing"
)
func TestNumFractionalDigitsLegacy(t *testing.T) {
tests := []struct {
name string
v Value
want int
}{
{
name: "over the default precision",
v: MustNewFromString("0.123456789"),
want: 8,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.v.NumFractionalDigits(); got != tt.want {
t.Errorf("NumFractionalDigitsLegacy() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -86,97 +86,17 @@ func TestFromString(t *testing.T) {
assert.Equal(t, "0.004075", f.String())
f = MustNewFromString("0.03")
assert.Equal(t, "0.03", f.String())
}
func TestDelta(t *testing.T) {
f1 := MustNewFromString("0.0009763593380614657")
f2 := NewFromInt(42300)
assert.InDelta(t, f1.Mul(f2).Float64(), 41.3, 1e-14)
f = MustNewFromString("0.75%")
assert.Equal(t, "0.0075", f.String())
}
// Not used
/*func TestParse(t *testing.T) {
type args struct {
input string
}
tests := []struct {
name string
args args
wantNum int64
wantNumDecimalPoints int
wantErr bool
}{
{
args: args{input: "-99.9"},
wantNum: -999,
wantNumDecimalPoints: 1,
wantErr: false,
},
{
args: args{input: "0.75%"},
wantNum: 75,
wantNumDecimalPoints: 4,
wantErr: false,
},
{
args: args{input: "0.12345678"},
wantNum: 12345678,
wantNumDecimalPoints: 8,
wantErr: false,
},
{
args: args{input: "a"},
wantNum: 0,
wantNumDecimalPoints: 0,
wantErr: true,
},
{
args: args{input: "0.1"},
wantNum: 1,
wantNumDecimalPoints: 1,
wantErr: false,
},
{
args: args{input: "100"},
wantNum: 100,
wantNumDecimalPoints: 0,
wantErr: false,
},
{
args: args{input: "100.9999"},
wantNum: 1009999,
wantNumDecimalPoints: 4,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotNum, gotNumDecimalPoints, err := Parse(tt.args.input)
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotNum != tt.wantNum {
t.Errorf("Parse() gotNum = %v, want %v", gotNum, tt.wantNum)
}
if gotNumDecimalPoints != tt.wantNumDecimalPoints {
t.Errorf("Parse() gotNumDecimalPoints = %v, want %v", gotNumDecimalPoints, tt.wantNumDecimalPoints)
}
})
}
}*/
func TestNumFractionalDigits(t *testing.T) {
tests := []struct {
name string
v Value
want int
}{
{
name: "over the default precision",
v: MustNewFromString("0.123456789"),
want: 9,
},
{
name: "ignore the integer part",
v: MustNewFromString("123.4567"),

View File

@ -1,3 +1,6 @@
//go:build dnum
// +build dnum
// Copyright Suneido Software Corp. All rights reserved.
// Governed by the MIT license found in the LICENSE file.