shopspring / decimal

Arbitrary-precision fixed-point decimal numbers in Go
Other
6.39k stars 624 forks source link

NewFromFloatWithExponent does not always return decimal with specified exponent #273

Open helenfufu opened 2 years ago

helenfufu commented 2 years ago

Hi! I am running into some issues trying to create an expected decimal with exponent -2 for the purpose of testing. When using NewFromFloatWithExponent with a float that has 0 in the tenths place (and hundredths, thousandths, etc.), the result always has an exponent of 0: https://go.dev/play/p/l38Jne-MI8W

d := decimal.NewFromFloatWithExponent(12.0, -2)
fmt.Println("d.Exponent()", d.Exponent())

prints 0 (decimal representation 12 10^0) where I would expect -2 (decimal representation 1200 10^-2). Is this the correct behavior?

If the float has a nonzero number in the tenths place, the result gives the specified exponent in this case:

d := decimal.NewFromFloatWithExponent(12.1, -2)
fmt.Println("d.Exponent()", d.Exponent())

prints -2 as expected. But with

d := decimal.NewFromFloatWithExponent(12.5, -2)
fmt.Println("d.Exponent()", d.Exponent())

prints -1, which is again unexpected.

mwoss commented 2 years ago

Hi @helenfufu! I've never studied NewFromFloatWithExponent implementation before, but while looking at it and tests for that init function it seems it was intentional (even though it is still kinda weird for me too). There is an original PR https://github.com/shopspring/decimal/pull/77, that also links https://www.cockroachlabs.com/blog/rounding-implementations-in-go/ as a test source. @igrmk sorry for pinging you after 4 years since you created that PR, but are you able to help us with the OP question and elaborate on the implementation? I would be grateful. :))

igrmk commented 2 years ago

Hi everyone! I removed my previous explanation because it was not quite correct. Unfortunately I don't remember details. Generally I wrote NewFromFloatWithExponent method treating the exponent more like a precision. There was a proposal to rename it to NewFromFloatWithPrecision but it never happened. I wrote a document for myself to be able to reconstruct the algorithm, but it would take some time even for me to get into it again https://github.com/igrmk/floating-points/blob/master/floating-points.pdf

igrmk commented 2 years ago

The explanation of the difference between the exponents of 12.1 and 12.5 numbers. https://go.dev/play/p/nru77mDw2ac 12.1 is actually represented as 12.0999999999999996447286321199499070644378662109375 in float64 and we round it to 2 digits after a point. That's why we keep -2 exponent. 12.5 can be represented exactly and it has a decimal exponent -1. So we just calculate and use it.

krasi-georgiev commented 2 years ago

+1 on renaming to NewFromFloatWithPrecision, this also confused me quite a bit at first