diff --git a/fpdecimal.go b/fpdecimal.go index 63402a2..cb6b23f 100644 --- a/fpdecimal.go +++ b/fpdecimal.go @@ -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 } @@ -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 } @@ -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 } diff --git a/fpdecimal_test.go b/fpdecimal_test.go index 22a1b2b..efc2d01 100644 --- a/fpdecimal_test.go +++ b/fpdecimal_test.go @@ -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}, @@ -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) } } }) @@ -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) } }) } @@ -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") @@ -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) } }) }