mirror of
https://github.com/shopspring/decimal.git
synced 2024-11-22 12:30:49 +01:00
added divsion with rest and rounded division
Added a division based on integer division with rest. A simple rounded division aded.
This commit is contained in:
parent
9995dc92ee
commit
e4641fd8ba
2 changed files with 147 additions and 0 deletions
64
decimal.go
64
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
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue