Skip to content

Commit

Permalink
Refresh (#24)
Browse files Browse the repository at this point in the history
* refresh

* minmax
  • Loading branch information
nikolaydubina authored Oct 27, 2023
1 parent d6a5b40 commit fe979f3
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 34 deletions.
65 changes: 46 additions & 19 deletions fpdecimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,37 @@ type Decimal struct{ v int64 }

var Zero = Decimal{}

var multipliers = []int64{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}
var multipliers = [...]int64{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}

type integer interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64
}

// FractionDigits that operations will use.
// Warning, after change, existing variables are not updated.
// Likely you want to use this once per runtime and in `func init()`.
var FractionDigits uint8 = 3

func FromInt[T ~int | ~int8 | ~int16 | ~int32 | ~int64](v T) Decimal {
return Decimal{int64(v) * multipliers[FractionDigits]}
}
func FromInt[T integer](v T) Decimal { return Decimal{int64(v) * multipliers[FractionDigits]} }

func FromFloat[T ~float32 | ~float64](v T) Decimal {
func FromFloat[T float32 | float64](v T) Decimal {
return Decimal{int64(float64(v) * float64(multipliers[FractionDigits]))}
}

func FromIntScaled[T ~int | ~int8 | ~int16 | ~int32 | ~int64](v T) Decimal { return Decimal{int64(v)} }
// FromIntScaled expects value already scaled to minor units
func FromIntScaled[T integer](v T) Decimal { return Decimal{int64(v)} }

func FromString(s string) (Decimal, error) {
v, err := ParseFixedPointDecimal([]byte(s), FractionDigits)
return Decimal{v}, err
}

func (v *Decimal) UnmarshalJSON(b []byte) (err error) {
v.v, err = ParseFixedPointDecimal(b, FractionDigits)
return err
}

func (v Decimal) MarshalJSON() ([]byte, error) { return []byte(v.String()), nil }

func (a Decimal) Scaled() int64 { return a.v }

Expand All @@ -43,11 +58,9 @@ func (a Decimal) Mul(b Decimal) Decimal { return Decimal{v: a.v * b.v / multipli

func (a Decimal) Div(b Decimal) Decimal { return Decimal{v: a.v * multipliers[FractionDigits] / b.v} }

func (a Decimal) DivMod(b Decimal) (part, remainder Decimal) {
// reduce divisor to avoid overflow of a at larger values
k := b.v / multipliers[FractionDigits]
return Decimal{v: a.v / k}, Decimal{v: a.v % k}
}
func (a Decimal) Mod(b Decimal) Decimal { return Decimal{v: a.v % (b.v / multipliers[FractionDigits])} }

func (a Decimal) DivMod(b Decimal) (part, remainder Decimal) { return a.Div(b), a.Mod(b) }

func (a Decimal) Equal(b Decimal) bool { return a.v == b.v }

Expand All @@ -69,14 +82,28 @@ func (a Decimal) Compare(b Decimal) int {
return 0
}

func FromString(s string) (Decimal, error) {
v, err := ParseFixedPointDecimal([]byte(s), FractionDigits)
return Decimal{v}, err
func Min(vs ...Decimal) Decimal {
if len(vs) == 0 {
panic("min of empty set is undefined")
}
var v Decimal = vs[0]
for _, q := range vs {
if q.LessThan(v) {
v = q
}
}
return v
}

func (v *Decimal) UnmarshalJSON(b []byte) (err error) {
v.v, err = ParseFixedPointDecimal(b, FractionDigits)
return err
func Max(vs ...Decimal) Decimal {
if len(vs) == 0 {
panic("max of empty set is undefined")
}
var v Decimal = vs[0]
for _, q := range vs {
if q.GreaterThan(v) {
v = q
}
}
return v
}

func (v Decimal) MarshalJSON() ([]byte, error) { return []byte(v.String()), nil }
103 changes: 88 additions & 15 deletions fpdecimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
fp "github.com/nikolaydubina/fpdecimal"
)

var multipliers = [...]int64{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}

func FuzzArithmetics(f *testing.F) {
tests := [][2]int64{
{1, 2},
Expand Down Expand Up @@ -69,10 +71,10 @@ func FuzzArithmetics(f *testing.F) {
t.Error(p, pdiv)
}
if p != fp.FromIntScaled(a/b) {
t.Error(a, b, p, r, a/b)
t.Error(a, b, p, a/b)
}
if r != fp.FromIntScaled(a%b) {
t.Error(a, b, p, r, a%b)
if fr := fp.FromIntScaled(a % b); r != fr {
t.Error("Mod", "a", a, "b", b, "got", fr, "want", r)
}
}
})
Expand Down Expand Up @@ -175,11 +177,11 @@ func FuzzToFloat(f *testing.F) {
a := fp.FromFloat(v)

if float32(v) != a.Float32() {
t.Error(a, a.Float32(), float32(v))
t.Error("a", a, "a.f32", a.Float32(), "f32.v", float32(v))
}

if v != a.Float64() {
t.Error(a, a.Float32(), v)
t.Error("a", a, "a.f32", a.Float32(), "v", v)
}
})
}
Expand Down Expand Up @@ -464,22 +466,93 @@ func ExampleDecimal_skip_trailing_zeros() {
// Output: 102.002
}

func ExampleDecimal_Div_remainder() {
func ExampleDecimal_Div() {
x, _ := fp.FromString("1.000")

a, r := x.DivMod(fp.FromInt(3))
fmt.Println(a, r)
// Output: 0.333 0.001
p := x.Div(fp.FromInt(3))
fmt.Print(p)
// Output: 0.333
}

func ExampleDecimal_Div_whole() {
x, _ := fp.FromString("1.000")
p := x.Div(fp.FromInt(5))
fmt.Print(p)
// Output: 0.2
}

func ExampleDecimal_Mod() {
x, _ := fp.FromString("1.000")
m := x.Mod(fp.FromInt(3))
fmt.Print(m)
// Output: 0.001
}

func ExampleDecimal_DivMod() {
x, _ := fp.FromString("1.000")
p, m := x.DivMod(fp.FromInt(3))
fmt.Print(p, m)
// Output: 0.333 0.001
}

a, r := x.DivMod(fp.FromInt(5))
fmt.Println(a, r)
func ExampleDecimal_DivMod_whole() {
x, _ := fp.FromString("1.000")
p, m := x.DivMod(fp.FromInt(5))
fmt.Print(p, m)
// Output: 0.2 0
}

func ExampleFromInt_uint8() {
var x uint8 = 100
v := fp.FromInt(x)
fmt.Print(v)
// Output: 100
}

func ExampleFromInt_int8() {
var x int8 = -100
v := fp.FromInt(x)
fmt.Print(v)
// Output: -100
}

func ExampleFromInt_int() {
var x int = -100
v := fp.FromInt(x)
fmt.Print(v)
// Output: -100
}

func ExampleFromInt_uint() {
var x uint = 100
v := fp.FromInt(x)
fmt.Print(v)
// Output: 100
}

func ExampleMin() {
min := fp.Min(fp.FromInt(100), fp.FromFloat(0.999), fp.FromFloat(100.001))
fmt.Print(min)
// Output: 0.999
}

func ExampleMin_empty() {
defer func() { fmt.Print(recover()) }()
fp.Min()
// Output: min of empty set is undefined
}

func ExampleMax() {
max := fp.Max(fp.FromInt(100), fp.FromFloat(0.999), fp.FromFloat(100.001))
fmt.Print(max)
// Output: 100.001
}

func ExampleMax_empty() {
defer func() { fmt.Print(recover()) }()
fp.Max()
// Output: max of empty set is undefined
}

func BenchmarkArithmetic(b *testing.B) {
x, _ := fp.FromString("251.231")
y, _ := fp.FromString("21231.001")
Expand Down Expand Up @@ -541,21 +614,21 @@ func TestSetFractionDigits(t *testing.T) {

t.Run("default 3", func(t *testing.T) {
if a, err := fp.FromString("1.123"); a.String() != "1.123" || err != nil {
t.Error("SetFractionDigits", a.String())
t.Error("SetFractionDigits", a)
}
})

t.Run("5", func(t *testing.T) {
fp.FractionDigits = 5
if a, err := fp.FromString("1.123456"); a.String() != "1.12345" || err != nil {
t.Error("SetFractionDigits 5", a.String())
t.Error("SetFractionDigits 5", a)
}
})

t.Run("10", func(t *testing.T) {
fp.FractionDigits = 10
if a, err := fp.FromString("1.12345678910"); a.String() != "1.1234567891" || err != nil {
t.Error("SetFractionDigits 10", a.String())
t.Error("SetFractionDigits 10", a)
}
})
}

0 comments on commit fe979f3

Please sign in to comment.