diff --git a/fpdecimal_test.go b/fpdecimal_test.go index 84ce527..79ae090 100644 --- a/fpdecimal_test.go +++ b/fpdecimal_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "math" + "strconv" "testing" "unsafe" @@ -78,7 +79,7 @@ func FuzzArithmetics(f *testing.F) { } func FuzzParse_StringSameAsFloat(f *testing.F) { - tests := []float32{ + tests := []float64{ 0, 0.100, 0.101, @@ -100,27 +101,28 @@ func FuzzParse_StringSameAsFloat(f *testing.F) { f.Add(tc) f.Add(-tc) } - f.Fuzz(func(t *testing.T, r float32) { + f.Fuzz(func(t *testing.T, r float64) { if r > math.MaxInt64/1000 || r < math.MinInt64/1000 { t.Skip() } s := fmt.Sprintf("%.3f", r) + rs, _ := strconv.ParseFloat(s, 64) v, err := fp.FromString(s) if err != nil { - t.Errorf(err.Error()) + t.Error(err) } - if s == "-0.000" || s == "0.000" || r == 0 || r == -0 { + if s == "-0.000" || s == "0.000" || rs == 0 || rs == -0 || (rs > -0.001 && rs < 0.001) { if v.String() != "0" { t.Errorf("s('0') != Decimal.String(%#v) of fp3(%#v) float32(%#v) .3f-float32(%#v)", v.String(), v, r, s) } return } - if s != v.String() { - t.Errorf("s(%#v) != Decimal.String(%#v) of fp3(%#v) float32(%#v)", s, v.String(), v, r) + if s, fs := strconv.FormatFloat(rs, 'f', -1, 64), v.String(); s != fs { + t.Error(s, fs, r, v) } }) } @@ -362,7 +364,8 @@ func FuzzJSON(f *testing.F) { } b := fmt.Sprintf("%.3f", v) - s := `{"a":` + b + `}` + rs, _ := strconv.ParseFloat(b, 64) + s := `{"a":` + strconv.FormatFloat(rs, 'f', -1, 64) + `}` var x MyType err := json.Unmarshal([]byte(s), &x) @@ -370,14 +373,14 @@ func FuzzJSON(f *testing.F) { t.Error(err, s) } - if b == "-0.000" || b == "0.000" || v == 0 || v == -0 { + if b == "-0.000" || b == "0.000" || rs == 0 || rs == -0 || (rs > 0.001 && rs < 0.001) { if x.A.String() != "0" { t.Error(b, x) } return } - if a := x.A.String(); a != b { + if a := x.A.String(); a != strconv.FormatFloat(rs, 'f', -1, 64) { t.Error(a, b) } @@ -413,6 +416,18 @@ func ExampleDecimal() { // Output: 18000.046 } +func ExampleDecimal_skip_whole_fraction() { + v, _ := fp.FromString("1013.0000") + fmt.Println(v) + // Output: 1013 +} + +func ExampleDecimal_skip_trailing_zeros() { + v, _ := fp.FromString("102.0020") + fmt.Println(v) + // Output: 102.002 +} + func ExampleDecimal_Div_remainder() { x, _ := fp.FromString("1.000") @@ -426,7 +441,7 @@ func ExampleDecimal_Div_whole() { a, r := x.DivMod(fp.FromInt(5)) fmt.Println(a, r) - // Output: 0.200 0 + // Output: 0.2 0 } func BenchmarkArithmetic(b *testing.B) { diff --git a/go.mod b/go.mod index 43004cd..f5338a5 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/nikolaydubina/fpdecimal -go 1.20 +go 1.21 diff --git a/printer.go b/printer.go index 168e3e2..8dec0e7 100644 --- a/printer.go +++ b/printer.go @@ -19,8 +19,6 @@ func FixedPointDecimalToString(v int64, p int) string { // AppendFixedPointDecimal appends formatted fixed point decimal to destination buffer. // Returns appended slice. // This is efficient for avoiding memory copy. -// strconv.AppendInt is very efficient. -// Efficient converting int64 to ASCII is not as trivial. func AppendFixedPointDecimal(b []byte, v int64, p int) []byte { if v == 0 { return append(b, '0') @@ -35,15 +33,20 @@ func AppendFixedPointDecimal(b []byte, v int64, p int) []byte { b = append(b, '-') } + // strconv.AppendInt is very efficient. + // Efficient converting int64 to ASCII is not as trivial. s := len(b) b = strconv.AppendInt(b, v, 10) + // has whole? if len(b)-s > p { + // place decimal point i := len(b) - p b = append(b, 0) copy(b[i+1:], b[i:]) b[i] = '.' } else { + // append zeroes and decimal point i := 2 + p - (len(b) - s) for j := 0; j < i; j++ { b = append(b, 0) @@ -52,5 +55,17 @@ func AppendFixedPointDecimal(b []byte, v int64, p int) []byte { copy(b[s:], []byte(zeroPrefix[:i])) } + // remove trailing zeros + n := 0 + for i, q := range b { + if q != '0' { + n = i + } + } + if b[n] == '.' { + n-- + } + b = b[:n+1] + return b } diff --git a/printer_test.go b/printer_test.go index ebe9ffd..5eb470a 100644 --- a/printer_test.go +++ b/printer_test.go @@ -11,7 +11,7 @@ import ( ) func FuzzFixedPointDecimalToString(f *testing.F) { - tests := []float32{ + tests := []float64{ 0, 0.100, 0.101, @@ -33,27 +33,28 @@ func FuzzFixedPointDecimalToString(f *testing.F) { f.Add(tc) f.Add(-tc) } - f.Fuzz(func(t *testing.T, r float32) { + f.Fuzz(func(t *testing.T, r float64) { if r > math.MaxInt64/1000 || r < math.MinInt64/1000 { t.Skip() } s := fmt.Sprintf("%.3f", r) + rs, _ := strconv.ParseFloat(s, 64) v, err := fpdecimal.ParseFixedPointDecimal(s, 3) if err != nil { t.Errorf(err.Error()) } - if s == "-0.000" || s == "0.000" || r == 0 || r == -0 { + if s == "-0.000" || s == "0.000" || rs == 0 || rs == -0 || (rs > -0.001 && rs < 0.001) { if q := fpdecimal.FixedPointDecimalToString(v, 3); q != "0" { t.Error(r, s, q) } return } - if fs := fpdecimal.FixedPointDecimalToString(v, 3); s != fs { - t.Error(s, fs, f, r, v) + if s, fs := strconv.FormatFloat(rs, 'f', -1, 64), fpdecimal.FixedPointDecimalToString(v, 3); s != fs { + t.Error(s, fs, r, v) } }) }