mirror of
https://github.com/shopspring/decimal.git
synced 2024-11-23 04:40:49 +01:00
Implemented: normalizing, rounding to significant figures
This commit is contained in:
parent
9aa42f1878
commit
f62241cc93
2 changed files with 153 additions and 0 deletions
54
decimal.go
54
decimal.go
|
@ -910,6 +910,60 @@ func (d Decimal) Truncate(precision int32) Decimal {
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoundToSignificantFigures returns a copy of a decimal rounded to specified number of significant figures.
|
||||||
|
// For negative or zero figures the function returns zero.
|
||||||
|
// Result is normalized without trailing zeros.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// NewFromString("5.45").RoundToSignificantFigures(2).String() // output: "5.5"
|
||||||
|
// NewFromString("545").RoundToSignificantFigures(2).String() // output: "550"
|
||||||
|
//
|
||||||
|
func (d Decimal) RoundToSignificantFigures(figures int32) Decimal {
|
||||||
|
if figures <= 0 {
|
||||||
|
return Zero
|
||||||
|
}
|
||||||
|
d = d.Normalize()
|
||||||
|
twoMant := big.NewInt(0).Set(d.value)
|
||||||
|
twoMant.Abs(twoMant)
|
||||||
|
twoMant.Mul(twoMant, twoInt)
|
||||||
|
upper := big.NewInt(int64(figures))
|
||||||
|
upper.Exp(tenInt, upper, nil)
|
||||||
|
upper.Mul(upper, twoInt)
|
||||||
|
upper.Sub(upper, oneInt)
|
||||||
|
m := int64(0)
|
||||||
|
for twoMant.Cmp(upper) >= 0 {
|
||||||
|
upper.Mul(upper, tenInt)
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
if int64(d.exp)+m > int64(math.MaxInt32) {
|
||||||
|
panic(fmt.Sprintf("exponent %d overflows an int32", int64(d.exp)+m))
|
||||||
|
}
|
||||||
|
return d.Round(-d.exp - int32(m)).Normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize normalizes decimal value.
|
||||||
|
// It strips all the trailing zeros.
|
||||||
|
// More specifically if a mantissa is representable as m*10^n it returns a decimal with a mantissa m and its exponent incremented by n.
|
||||||
|
func (d Decimal) Normalize() Decimal {
|
||||||
|
d.ensureInitialized()
|
||||||
|
quo := big.NewInt(0).Set(d.value)
|
||||||
|
prevQuo := big.NewInt(0).Set(d.value)
|
||||||
|
rem := big.NewInt(0)
|
||||||
|
n := int64(0)
|
||||||
|
for {
|
||||||
|
quo.QuoRem(quo, tenInt, rem)
|
||||||
|
if rem.Cmp(zeroInt) != 0 || quo.Cmp(zeroInt) == 0 {
|
||||||
|
if int64(d.exp)+n > int64(math.MaxInt32) {
|
||||||
|
panic(fmt.Sprintf("exponent %d overflows an int32", int64(d.exp)+n))
|
||||||
|
}
|
||||||
|
return Decimal{value: prevQuo, exp: d.exp + int32(n)}
|
||||||
|
}
|
||||||
|
prevQuo.Set(quo)
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error {
|
func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error {
|
||||||
if string(decimalBytes) == "null" {
|
if string(decimalBytes) == "null" {
|
||||||
|
|
|
@ -2577,6 +2577,105 @@ func TestTan(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalize(t *testing.T) {
|
||||||
|
tbl := []struct {
|
||||||
|
inp Decimal
|
||||||
|
res Decimal
|
||||||
|
}{
|
||||||
|
{New(0, 0), New(0, 0)},
|
||||||
|
{New(10, 0), New(1, 1)},
|
||||||
|
{New(100, 0), New(1, 2)},
|
||||||
|
{New(11, 0), New(11, 0)},
|
||||||
|
{New(111, 0), New(111, 0)},
|
||||||
|
{New(-10, 0), New(-1, 1)},
|
||||||
|
{New(-100, 0), New(-1, 2)},
|
||||||
|
{New(-11, 0), New(-11, 0)},
|
||||||
|
{New(-111, 0), New(-111, 0)},
|
||||||
|
{New(0, 5), New(0, 0)},
|
||||||
|
{New(10, 5), New(1, 6)},
|
||||||
|
{New(100, 5), New(1, 7)},
|
||||||
|
{New(11, 5), New(11, 5)},
|
||||||
|
{New(111, 5), New(111, 5)},
|
||||||
|
{New(-10, 5), New(-1, 6)},
|
||||||
|
{New(-100, 5), New(-1, 7)},
|
||||||
|
{New(-11, 5), New(-11, 5)},
|
||||||
|
{New(-111, 5), New(-111, 5)},
|
||||||
|
{New(0, -5), New(0, 0)},
|
||||||
|
{New(10, -5), New(1, -4)},
|
||||||
|
{New(100, -5), New(1, -3)},
|
||||||
|
{New(11, -5), New(11, -5)},
|
||||||
|
{New(111, -5), New(111, -5)},
|
||||||
|
{New(-10, -5), New(-1, -4)},
|
||||||
|
{New(-100, -5), New(-1, -3)},
|
||||||
|
{New(-11, -5), New(-11, -5)},
|
||||||
|
{New(-111, -5), New(-111, -5)},
|
||||||
|
}
|
||||||
|
for _, i := range tbl {
|
||||||
|
if norm := i.inp.Normalize(); norm.Cmp(i.res) != 0 {
|
||||||
|
t.Errorf("unexpected normalization result, expected: %v, got: %v", i.res, norm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundToSignificantFigures(t *testing.T) {
|
||||||
|
type testData struct {
|
||||||
|
inp Decimal
|
||||||
|
fig int32
|
||||||
|
res Decimal
|
||||||
|
}
|
||||||
|
strtbl := []struct {
|
||||||
|
inp string
|
||||||
|
fig int32
|
||||||
|
res string
|
||||||
|
}{
|
||||||
|
{"5.45", 2, "5.5"},
|
||||||
|
{"545", 2, "550"},
|
||||||
|
{"0", 0, "0"},
|
||||||
|
{"0", 1, "0"},
|
||||||
|
{"0", -1, "0"},
|
||||||
|
{"1", 0, "0"},
|
||||||
|
{"1", 1, "1"},
|
||||||
|
{"1", 2, "1"},
|
||||||
|
{"1", -1, "0"},
|
||||||
|
{"10", 1, "10"},
|
||||||
|
{"10", 0, "0"},
|
||||||
|
{"100", 1, "100"},
|
||||||
|
{"110", 1, "100"},
|
||||||
|
{"110", 2, "110"},
|
||||||
|
{"995", 1, "1000"},
|
||||||
|
{"995", 2, "1000"},
|
||||||
|
{"994", 2, "990"},
|
||||||
|
{"995", 3, "995"},
|
||||||
|
{"995", 4, "995"},
|
||||||
|
{"0.995", 1, "1"},
|
||||||
|
{"0.995", 2, "1"},
|
||||||
|
{"0.994", 2, "0.99"},
|
||||||
|
{"0.995", 3, "0.995"},
|
||||||
|
{"0.995", 4, "0.995"},
|
||||||
|
{"994.99999999999999999999999999", 2, "990"},
|
||||||
|
{"994.999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 2, "990"},
|
||||||
|
{"995.999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 2, "1000"},
|
||||||
|
{"0.101", 1, "0.1"},
|
||||||
|
{"0.101", 2, "0.1"},
|
||||||
|
{"0.101", 3, "0.101"},
|
||||||
|
}
|
||||||
|
tbl := []testData{{Decimal{}, 1, Decimal{}}}
|
||||||
|
for _, x := range strtbl {
|
||||||
|
tbl = append(tbl, testData{RequireFromString(x.inp), x.fig, RequireFromString(x.res)})
|
||||||
|
// Symmetric negative tests
|
||||||
|
if x.inp != "0" {
|
||||||
|
x.inp = "-" + x.inp
|
||||||
|
x.res = "-" + x.res
|
||||||
|
tbl = append(tbl, testData{RequireFromString(x.inp), x.fig, RequireFromString(x.res)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, x := range tbl {
|
||||||
|
if rnd := x.inp.RoundToSignificantFigures(x.fig); rnd.Cmp(x.res) != 0 {
|
||||||
|
t.Errorf("unexpected rounding to significant figures result, expected: %s, got: %v", x.res, rnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleNewFromFloat32() {
|
func ExampleNewFromFloat32() {
|
||||||
fmt.Println(NewFromFloat32(123.123123123123).String())
|
fmt.Println(NewFromFloat32(123.123123123123).String())
|
||||||
fmt.Println(NewFromFloat32(.123123123123123).String())
|
fmt.Println(NewFromFloat32(.123123123123123).String())
|
||||||
|
|
Loading…
Reference in a new issue