quagmt / udecimal

A high-performance, high precision, zero allocation fixed-point decimal library for financial applications
https://quagmt.github.io/udecimal/
BSD 3-Clause "New" or "Revised" License
111 stars 4 forks source link

StringFixed fixed rounding does not work as expected for fixed numbers #23

Closed matejsp closed 4 days ago

matejsp commented 5 days ago

StringFixed should return with fixed precision but it is not working for fixed numbers as expected.

t.Run("StringFixed", func(t *testing.T) {
    a1 := udecimal.MustParse("10")
    a2 := udecimal.MustParse("1")
    a3 := udecimal.MustParse("1.1")
    a4 := udecimal.MustParse("1.11")

    assert.Equal(t, a1.StringFixed(3), "10.000") // returns 10 ...
    assert.Equal(t, a2.StringFixed(3), "1.000") // return 1
    assert.Equal(t, a3.StringFixed(3), "1.100") // return 1.100
    assert.Equal(t, a4.StringFixed(3), "1.110") // return 1.110
})
quagmt commented 5 days ago

Thanks for reporting this. Looks like I missed this case. However, please notice that this StringFixed version behaves a little bit different than shopspring/decimal. In shopspring/decimal, StringFixed will round the result before printing, while this library just keep the result as it is (any rouding should be performed before calling this method)

   // shopspring/decimal
   a := decimal.RequireFromString("1.555")
   fmt.Println(a.StringFixed(2)) // 1.56

   // udecimal
   b := udecimal.MustParse("1.555")
   fmt.Println(b.StringFixed(2)) // 1.555
matejsp commented 5 days ago

Yes I noticed that I need to round before and that's fine. Another difference I noticed that shopspring supports negative precision (so you can round to -1 => 10s or -2 => 100s). But your round supports only rounding on decimal part.

// StringFixed returns a rounded fixed-point string with places digits after
// the decimal point.
//
// Example:
//
//  NewFromFloat(0).StringFixed(2) // output: "0.00"
//  NewFromFloat(0).StringFixed(0) // output: "0"
//  NewFromFloat(5.45).StringFixed(0) // output: "5"
//  NewFromFloat(5.45).StringFixed(1) // output: "5.5"
//  NewFromFloat(5.45).StringFixed(2) // output: "5.45"
//  NewFromFloat(5.45).StringFixed(3) // output: "5.450"
//  NewFromFloat(545).StringFixed(-1) // output: "550"
func (d Decimal) StringFixed(places int32) string {
    rounded := d.Round(places)
    return rounded.string(false)
}
quagmt commented 5 days ago

All rounding methods don't support negative value. I'm not sure if rounding to power of 10 is necessary or not. Might be an improvement for v2 if it's actually needed.