diff --git a/decimal.go b/decimal.go index e8d99b4..241bbee 100644 --- a/decimal.go +++ b/decimal.go @@ -287,6 +287,70 @@ func (d Decimal) Div(d2 Decimal) Decimal { return ret } +// return quotient q and remainder r such that +// scale := -precision +// d = d2 * q + r such that q an integral multiple of 10^scale +// and 0 <= r < abs(d2) * 10 ^scale +// this holds if d>=0 +// if d <0 then r =<0 with r>= -abs(d2) * 10 ^ scale +func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) { + scale := -precision + e := int64(d.exp - d2.exp - scale) + var aa, bb, expo big.Int + var scalerest int32 + // d = a 10^ea + // d2 = b 10^eb + if e < 0 { + aa = *d.value + expo.SetInt64(-e) + bb.Exp(tenInt, &expo, nil) + bb.Mul(d2.value, &bb) + scalerest = d.exp + // now aa = a + // bb = b 10^(scale + eb - ea) + } else { + expo.SetInt64(e) + aa.Exp(tenInt, &expo, nil) + aa.Mul(d.value, &aa) + bb = *d2.value + scalerest = scale + d2.exp + // now aa = a ^ (ea - eb - scale) + // bb = b + } + var q, r big.Int + q.QuoRem(&aa, &bb, &r) + dq := Decimal{value: &q, exp: scale} + dr := Decimal{value: &r, exp: scalerest} + return dq, dr +} + +// divide by d by d2 and round to precision digits after . +// i.e. to an integer multiple of 10^(-precision) +// for positive numbers normal rounding, 5 is rounded up, +// if the quotient is negative then 5 is rounded down +func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal { + q, r := d.QuoRem(d2, precision) + // the actual rounding decision is based on comparing r*10^precision and d2/2 + // instead compare 2 r 10 ^precision and d2 + var rv2 big.Int + rv2.Abs(r.value) + rv2.Lsh(&rv2, 1) + // now rv2 = abs(r.value) * 2 + r2 := Decimal{value: &rv2, exp: r.exp + precision} + // r2 is now 2 * r * 10 ^ precision + var c = r2.Cmp(d2.Abs()) + + if c < 0 { + return q + } else { + if d.value.Sign()*d2.value.Sign() < 0 { + return q.Sub(New(1, -precision)) + } else { + return q.Add(New(1, -precision)) + } + } +} + // Cmp compares the numbers represented by d and d2 and returns: // // -1 if d < d2 diff --git a/decimal_test.go b/decimal_test.go index 5202234..53f2f5c 100644 --- a/decimal_test.go +++ b/decimal_test.go @@ -644,6 +644,10 @@ func TestDecimal_Div(t *testing.T) { t.Errorf("expected %s when dividing %v by %v, got %v", expected, num, denom, got) } + got2 := num.DivRound(denom, int32(DivisionPrecision)) + if !got.Equals(got2) { + t.Errorf("aua: %s %s", got.String(), got2.String()) + } } type Inp2 struct { @@ -683,6 +687,85 @@ func TestDecimal_Div(t *testing.T) { } } +func TestDecimal_QuoRem(t *testing.T) { + type Inp4 struct { + d string + d2 string + exp int32 + q string + r string + } + cases := []Inp4{ + Inp4{"10", "1", 0, "10", "0"}, + Inp4{"1", "10", 0, "0", "1"}, + Inp4{"1", "4", 2, "0.25", "0"}, + Inp4{"1", "8", 2, "0.12", "0.04"}, + Inp4{"10", "3", 1, "3.3", "0.1"}, + Inp4{"100", "3", 1, "33.3", "0.1"}, + Inp4{"1000", "10", -3, "0", "1000"}, + Inp4{"1e-3", "2e-5", 0, "50", "0"}, + Inp4{"1e-3", "2e-3", 1, "0.5", "0"}, + Inp4{"4e-3", "0.8", 4, "5e-3", "0"}, + Inp4{"4.1e-3", "0.8", 3, "5e-3", "1e-4"}, + Inp4{"-4", "-3", 0, "1", "-1"}, + Inp4{"-4", "3", 0, "-1", "-1"}, + } + + for _, inp4 := range cases { + d, _ := NewFromString(inp4.d) + d2, _ := NewFromString(inp4.d2) + exp := inp4.exp + q, r := d.QuoRem(d2, exp) + expectedQ, _ := NewFromString(inp4.q) + expectedR, _ := NewFromString(inp4.r) + if !q.Equals(expectedQ) || !r.Equals(expectedR) { + t.Errorf("bad division %s %s %d -> %s %s", + inp4.d, inp4.d2, exp, q.String(), r.String()) + } + if !d.Equals(d2.Mul(q).Add(r)) { + t.Errorf("not fitting") + } + if !q.Equals(q.Truncate(exp)) { + t.Errorf("Quotient wrong") + } + if r.Abs().Cmp(d2.Abs().Mul(New(1, -exp))) >= 0 { + t.Errorf("rem too large") + } + if r.value.Sign()*d.value.Sign() < 0 { + t.Errorf("signum of divisor and rest do not match") + } + } +} + +func TestDecimal_DivRound(t *testing.T) { + cases := []struct { + d string + d2 string + scale int32 + result string + }{ + {"2", "2", 0, "1"}, + {"1", "2", 0, "1"}, + {"-1", "2", 0, "-1"}, + {"-1", "-2", 0, "1"}, + {"1", "-2", 0, "-1"}, + {"1", "-2", 0, "-1"}, + {"1", "-20", 1, "-0.1"}, + {"1", "-20", 2, "-0.05"}, + {"1", "20.0000000000000000001", 1, "0"}, + {"1", "19.9999999999999999999", 1, "0.1"}, + } + for _, s := range cases { + d, _ := NewFromString(s.d) + d2, _ := NewFromString(s.d2) + result, _ := NewFromString(s.result) + q := d.DivRound(d2, s.scale) + if !q.Equals(result) { + t.Errorf("division wrong %s / %s scale %d = %s, got %s", s.d, s.d2, s.scale, s.result, q.String()) + } + } +} + func TestDecimal_Overflow(t *testing.T) { if !didPanic(func() { New(1, math.MinInt32).Mul(New(1, math.MinInt32)) }) { t.Fatalf("should have gotten an overflow panic")