In Kotlin 123.456 is a valid Double value
Actually, that's not quite true. There's a Double value very close to 123.456, but it's not exactly 123.456. What you're seeing is the consequences of that.
So you can't maintain precision, because you don't have that precision to start with!
Short answer:
If you need exact values, don't use floating-point!
(In particular: Never store money values in floating-point! See for example this question.)
The best alternative is usually BigDecimal
which can store and calculate decimal fractions to an arbitrary precision. They're less efficient, but Kotlin's operator overloading makes them painless to use (unlike Java!).
Or if you're not going to be doing any calculations, you could store them as String
s.
Or if you'll only need a certain number of decimal places, you could scale them all up to Int
s (or Long
s).
Technical explanation:
Floats and Doubles use binary floating-point; they store an integer, and an integer power of 2 to multiple or divide it by. (For example, 3/4 would be stored as 3*2⁻².) This means they can store a wide range of binary fractions exactly.
However, just as you can't store 1/3 as a decimal fraction (it's 0.3333333333…, but any finite number of digits will only be an approximation), so you can't store 1/10 as a binary fraction (it's 0.000110011001100…). This means that a binary floating-point number can't store most decimal numbers exactly.
Instead, they store the nearest possible value to the number you want. And the routines which convert them to a String will try to undo that difference, by rounding appropriately. But that doesn't always give the result you expect.
Floating-point numbers are great when you need a huge range of values (e.g. in scientific and technical use), but don't care about storing them exactly.