Skip to content

Instantly share code, notes, and snippets.

@maxpushka
Created December 6, 2023 10:08
Show Gist options
  • Save maxpushka/1a82e5bed4ab16d63379af7de349cc2d to your computer and use it in GitHub Desktop.
Save maxpushka/1a82e5bed4ab16d63379af7de349cc2d to your computer and use it in GitHub Desktop.
package precision
import (
"math"
"github.com/shopspring/decimal"
)
func ToSignificant(input decimal.Decimal, sigDigits int32, maxScale int32) decimal.Decimal {
if input.Equal(decimal.Zero) {
return input
}
integralDigits := int32(len(input.Abs().Coefficient().String()))
scale := input.Exponent()
if scale > 0 {
scale = 0
} else {
scale = -scale
}
adjustedScale := sigDigits - (integralDigits - scale)
if adjustedScale < 0 {
// If the number of integral digits is greater than significant digits,
// round the number to a scale that maintains the significant digits in the integral part.
roundedValue := input.RoundBank(0)
multiplier := decimal.NewFromInt(int64(math.Pow10(int(adjustedScale * -1))))
return roundedValue.DivRound(multiplier, 0).Mul(multiplier)
} else if adjustedScale > maxScale {
adjustedScale = maxScale
}
return input.RoundBank(adjustedScale)
}
package precision
import (
"fmt"
"math"
"testing"
"github.com/shopspring/decimal"
)
func TestToSignificant(t *testing.T) {
tests := []struct {
input decimal.Decimal
expected decimal.Decimal
}{
{decimal.NewFromFloat(0.0), decimal.NewFromFloat(0.0)},
{decimal.NewFromFloat(math.SmallestNonzeroFloat64), decimal.NewFromFloat(0.0)},
{decimal.NewFromFloat(0.0000000000000004), decimal.NewFromFloat(0.0000000000000004)},
{decimal.NewFromFloat(0.00000000000000004), decimal.NewFromFloat(0)},
{decimal.NewFromFloat(0.0001), decimal.NewFromFloat(0.0001)},
{decimal.NewFromFloat(0.000103456), decimal.NewFromFloat(0.00010346)},
{decimal.NewFromFloat(0.012344789), decimal.NewFromFloat(0.012345)},
{decimal.NewFromFloat(0.001023499), decimal.NewFromFloat(0.0010235)},
{decimal.NewFromFloat(0.012345), decimal.NewFromFloat(0.012345)},
{decimal.NewFromFloat(1.00000234), decimal.NewFromFloat(1.0)},
{decimal.NewFromInt(2), decimal.NewFromInt(2)},
{decimal.NewFromInt(100006), decimal.NewFromInt(100010)},
// Two more cases
{decimal.NewFromFloat(0.012345928), decimal.NewFromFloat(0.012346)},
{decimal.NewFromFloat(0.001023499), decimal.NewFromFloat(0.0010235)},
}
for _, tt := range tests {
tt := tt
t.Run(fmt.Sprintf("%s -> %s", tt.input, tt.expected), func(t *testing.T) {
actual := ToSignificant(tt.input, 5, 16)
if !actual.Equals(tt.expected) {
t.Errorf("ToSignificant(%s): expected %s, got %s", tt.input, tt.expected, actual)
}
})
}
}
func BenchmarkToSignificant_DecimalWithLeadingZeros(b *testing.B) {
// Reset timer to exclude time taken by setup operations
// before the actual benchmark begins
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ToSignificant(decimal.NewFromFloat(0.000123456), 5, 16)
}
}
func BenchmarkToSignificant_TruncateDecimals(b *testing.B) {
// Reset timer to exclude time taken by setup operations
// before the actual benchmark begins
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ToSignificant(decimal.NewFromFloat(1.00000234), 5, 16)
}
}
func BenchmarkToSignificant_IntegralPartSizeGreaterThanSignificantDigits(b *testing.B) {
// Reset timer to exclude time taken by setup operations
// before the actual benchmark begins
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ToSignificant(decimal.NewFromInt(100006), 5, 16)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment