Skip to content

Commit

Permalink
Merge pull request #17 from govalues/new-from-int64-method
Browse files Browse the repository at this point in the history
decimal: implement NewFromInt64 method
  • Loading branch information
eapenkin authored Aug 4, 2023
2 parents 7d5889c + e812bb9 commit 84cdef3
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 18 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [0.1.4] - 2023-08-04

### Changed

- Implemented NewFromInt64 method.

## [0.1.3] - 2023-08-03

### Changed
Expand Down
44 changes: 39 additions & 5 deletions decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func newDecimalFromSint(neg bool, coef *sint, scale, minScale int) (Decimal, err
return newDecimalSafe(neg, coef.fint(), scale)
}

// New returns a (possibly rounded) decimal equal to coef / 10^scale.
// New returns a decimal equal to coef / 10^scale.
//
// New returns an error if scale is negative or more than [MaxScale].
func New(coef int64, scale int) (Decimal, error) {
Expand All @@ -153,6 +153,39 @@ func MustNew(coef int64, scale int) Decimal {
return d
}

// NewFromInt64 returns a decimal equal to base + frac / 10^scale.
// See also method [Decimal.Int64].
//
// NewFromInt64 returns an error:
// - if base and frac have different signs.
// - if scale is negative or more than [MaxScale].
// - if frac / 10^scale is not within the range (-1, 1).
// - if the integer part of the result has more than ([MaxPrec] - scale) digits.
func NewFromInt64(base, frac int64, scale int) (Decimal, error) {
if base > 0 && frac < 0 || base < 0 && frac > 0 {
return Decimal{}, fmt.Errorf("inconsistent signs")
}
// Integer
b, err := New(base, 0)
if err != nil {
return Decimal{}, fmt.Errorf("converting integers: %w", err)
}
// Fraction
f, err := New(frac, scale)
if err != nil {
return Decimal{}, fmt.Errorf("converting integers: %w", err)
}
if !f.WithinOne() {
return Decimal{}, fmt.Errorf("inconsistent fraction")
}
// Decimal
d, err := b.AddExact(f, f.MinScale())
if err != nil {
return Decimal{}, fmt.Errorf("converting integers: %w", err)
}
return d, nil
}

// Zero returns a decimal with a value of 0, having the same scale as decimal d.
func (d Decimal) Zero() Decimal {
return newDecimalUnsafe(false, 0, d.Scale())
Expand Down Expand Up @@ -434,14 +467,15 @@ func (d Decimal) Float64() (f float64, ok bool) {
return z, true
}

// Int64 returns a pair of int64 values representing the integer part i and the
// Int64 returns a pair of int64 values representing the integer part b and the
// fractional part f of the decimal.
// The relationship between the decimal and the returned values can be expressed
// as d = i + f / 10^scale.
// as d = b + f / 10^scale.
// If the result cannot be accurately represented as a pair of int64 values,
// the method returns false.
func (d Decimal) Int64(scale int) (i, f int64, ok bool) {
if scale < 0 {
// See also method [NewFromInt64].
func (d Decimal) Int64(scale int) (b, f int64, ok bool) {
if scale < 0 || scale > MaxScale {
return 0, 0, false
}
x := d.coef
Expand Down
124 changes: 111 additions & 13 deletions decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,65 @@ func TestNew(t *testing.T) {
})
}

func TestNewFromInt64(t *testing.T) {
t.Run("success", func(t *testing.T) {
tests := []struct {
base, frac int64
scale int
d string
}{
{0, 0, 0, "0"},
{0, 0, 1, "0.0"},
{0, 0, 2, "0.00"},
{0, 0, 19, "0.0000000000000000000"},

{-1, -1, 1, "-1.1"},
{-1, -1, 2, "-1.01"},
{-1, -1, 3, "-1.001"},
{-1, -1, 18, "-1.000000000000000001"},

{1, 1, 1, "1.1"},
{1, 1, 2, "1.01"},
{1, 1, 3, "1.001"},
{1, 1, 18, "1.000000000000000001"},
{100000000000000000, 100000000000000000, 18, "100000000000000000.1"},
}
for _, tt := range tests {
got, err := NewFromInt64(tt.base, tt.frac, tt.scale)
if err != nil {
t.Errorf("NewFromInt64(%v, %v, %v) failed: %v", tt.base, tt.frac, tt.scale, err)
continue
}
want := MustParse(tt.d)
if got != want {
t.Errorf("NewFromInt64(%v, %v, %v) = %q, want %q", tt.base, tt.frac, tt.scale, got, want)
}
}
})

t.Run("error", func(t *testing.T) {
tests := map[string]struct {
base, frac int64
scale int
}{
"different signs 1": {-1, 1, 0},
"fraction range 1": {1, 1, 0},
"scale range 1": {1, 1, -1},
"scale range 2": {1, 0, 20},
"overflow 1": {1, 1, 19},
"overflow 2": {999999999999999999, 1, 2},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
_, err := NewFromInt64(tt.base, tt.frac, tt.scale)
if err == nil {
t.Errorf("NewFromInt64(%v, %v, %v) did not fail", tt.base, tt.frac, tt.scale)
}
})
}
})
}

func TestParse(t *testing.T) {
t.Run("success", func(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -317,10 +376,10 @@ func TestDecimal_Float64(t *testing.T) {

func TestDecimal_Int64(t *testing.T) {
tests := []struct {
d string
scale int
wantInt, wantFrac int64
wantOk bool
d string
scale int
wantBase, wantFrac int64
wantOk bool
}{
// Zeroes
{"0", 0, 0, 0, true},
Expand Down Expand Up @@ -382,27 +441,30 @@ func TestDecimal_Int64(t *testing.T) {
// Edge cases
{"9223372036854775807", 0, 9223372036854775807, 0, true},
{"-9223372036854775808", 0, -9223372036854775808, 0, true},
{"9223372036854775808", 0, 0, 0, false},
{"-9223372036854775809", 0, 0, 0, false},
{"922337203685477580.8", 1, 922337203685477580, 8, true},
{"-922337203685477580.9", 1, -922337203685477580, -9, true},
{"9.223372036854775808", 18, 9, 223372036854775808, true},
{"-9.223372036854775809", 18, -9, -223372036854775809, true},
{"0.9223372036854775808", 19, 0, 0, false},
{"-0.9223372036854775809", 19, 0, 0, false},
{"0.9223372036854775807", 19, 0, 9223372036854775807, true},
{"-0.9223372036854775808", 19, 0, -9223372036854775808, true},

// Failures
{"9223372036854775808", 0, 0, 0, false},
{"-9223372036854775809", 0, 0, 0, false},
{"0.9223372036854775808", 19, 0, 0, false},
{"-0.9223372036854775809", 19, 0, 0, false},
{"9999999999999999999", 0, 0, 0, false},
{"-9999999999999999999", 0, 0, 0, false},
{"0.9999999999999999999", 19, 0, 0, false},
{"-0.9999999999999999999", 19, 0, 0, false},
{"0.1", -1, 0, 0, false},
{"0.1", 20, 0, 0, false},
}
for _, tt := range tests {
d := MustParse(tt.d)
gotInt, gotFrac, gotOk := d.Int64(tt.scale)
if gotInt != tt.wantInt || gotFrac != tt.wantFrac || gotOk != tt.wantOk {
t.Errorf("%q.Int64(%v) = [%v %v %v], want [%v %v %v]", d, tt.scale, gotInt, gotFrac, gotOk, tt.wantInt, tt.wantFrac, tt.wantOk)
gotBase, gotFrac, gotOk := d.Int64(tt.scale)
if gotBase != tt.wantBase || gotFrac != tt.wantFrac || gotOk != tt.wantOk {
t.Errorf("%q.Int64(%v) = [%v %v %v], want [%v %v %v]", d, tt.scale, gotBase, gotFrac, gotOk, tt.wantBase, tt.wantFrac, tt.wantOk)
}
}
}
Expand Down Expand Up @@ -2254,7 +2316,7 @@ func FuzzParse(f *testing.F) {
)
}

func FuzzDecimal_String_ParseVsString(f *testing.F) {
func FuzzDecimal_String_StringVsParse(f *testing.F) {
for _, d := range corpus {
f.Add(d.neg, d.scale, d.coef)
}
Expand All @@ -2274,14 +2336,50 @@ func FuzzDecimal_String_ParseVsString(f *testing.F) {
return
}

if got != want {
if got.CmpTotal(want) != 0 {
t.Errorf("Parse(%q) = %v, want %v", str, got, want)
return
}
},
)
}

func FuzzDecimal_Int64_Int64VsNew(f *testing.F) {
for _, d := range corpus {
for s := 0; s <= MaxScale; s++ {
f.Add(d.neg, d.scale, d.coef, s)
}
}

f.Fuzz(
func(t *testing.T, dneg bool, dscale int, dcoef uint64, scale int) {
want, err := newDecimalSafe(dneg, fint(dcoef), dscale)
if err != nil {
t.Skip()
return
}

base, frac, ok := want.Int64(scale)
if !ok {
t.Skip()
return
}

got, err := NewFromInt64(base, frac, scale)
if err != nil {
t.Errorf("NewFromInt64(%v, %v, %v) failed: %v", base, frac, scale, err)
return
}

want = want.Round(scale)
if got.Cmp(want) != 0 {
t.Errorf("NewFromInt64(%v, %v, %v) = %v, want %v", base, frac, scale, got, want)
return
}
},
)
}

func FuzzDecimal_Mul_FintVsSint(f *testing.F) {
for _, d := range corpus {
for _, e := range corpus {
Expand Down
12 changes: 12 additions & 0 deletions doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,18 @@ func ExampleNew() {
// -123 <nil>
}

func ExampleNewFromInt64() {
fmt.Println(decimal.NewFromInt64(-1, -23, 2))
fmt.Println(decimal.NewFromInt64(-1, -23, 3))
fmt.Println(decimal.NewFromInt64(-1, -23, 4))
fmt.Println(decimal.NewFromInt64(-1, -23, 5))
// Output:
// -1.23 <nil>
// -1.023 <nil>
// -1.0023 <nil>
// -1.00023 <nil>
}

func ExampleDecimal_Zero() {
d := decimal.MustParse("-1.23")
e := decimal.MustParse("0.4")
Expand Down

0 comments on commit 84cdef3

Please sign in to comment.