Implemented: normalizing, rounding to significant figures

This commit is contained in:
Igor Mikushkin 2018-09-04 04:34:19 +04:00
parent 9aa42f1878
commit f62241cc93
2 changed files with 153 additions and 0 deletions

View file

@ -910,6 +910,60 @@ func (d Decimal) Truncate(precision int32) Decimal {
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.
func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error {
if string(decimalBytes) == "null" {

View file

@ -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() {
fmt.Println(NewFromFloat32(123.123123123123).String())
fmt.Println(NewFromFloat32(.123123123123123).String())