fn main() {
println!("{:.2}", 4.305); // 4.30 -> innaccurate
println!("{:.2}", round_number(4.305)); // 4.31 -> accurate
}
pub fn round_number(nr: f64) -> f64 {
(nr * 100f64).round() / 100f64
}
Why isn't {:.2}
accurate by default?
fn main() {
println!("{:.2}", 4.305); // 4.30 -> innaccurate
println!("{:.2}", round_number(4.305)); // 4.31 -> accurate
}
pub fn round_number(nr: f64) -> f64 {
(nr * 100f64).round() / 100f64
}
Why isn't {:.2}
accurate by default?
To elaborate, this demonstrates the precise behavior at play relatively well:
fn main() {
println!("{:.2}", 4.305); // 4.30 -> âinnaccurateâ
println!("{:.80}", 4.305); // 4.30 -> full accuracy of *actual* float
}
4.30
4.30499999999999971578290569595992565155029296875000000000000000000000000000000000
In case you wonder now âwhy does multiplying by 100 give the more âcorrectâ seeming result then?â, the answer is, that the deviation between 4.30499999999999971578290569595992565155029296875
and 4.305
is so small, that the floating point number closest to 430.499999999999971578290569595992565155029296875
turns out to be exactly 430.5
.
fn main() {
println!("{:.80}", 430.499999999999971578290569595992565155029296875);
}
430.50000000000000000000000000000000000000000000000000000000000000000000000000000000
Finally, if you wonder, why does println!("{}", 4.305);
print something different than println!("{:.80}", 4.305)
, almost as if the float in question was exactly 4.305
, the answer to that question in turn is: When printing a float without specifying a number of digits, then the printing algorithm will choose only as many digits as are necessary in order to converting the result back into a float to create an accurate result. Since turning the decimal number 4.305
into a float will correctly result in the floating point number 4.30499999999999971578290569595992565155029296875
being generated, that means that 4.305
is a fine choice for printing it.
And if you're interested in further details, there is this gem and its accompanying article, which brightened my whole day when I first stumbled across them: the world just seems a better place when you discover something so well done.
Ah, very nice! Iâve googled for similar tools quickly, but didnât come across one where I could properly link to the number in question ^^
On top of the other good answers, it's worth noting that floating point is not meant to be a perfect representation (not even of numbers with an exact representation in binary); they are meant to be an approximation of the infinite set of real numbers into a finite set, designed to be amenable to numerical analysis and also to get "good enough" results most of the time if you're not doing numerical analysis of your algorithms.
In turn, numerical analysis is the field of mathematics that looks at how approximations behave algorithmically, and aims to either tell you that your algorithm can't be used to get useful results with an approximation, or what the error bound is for your algorithm. For example, naĂŻve floating point summation has an error bound that's proportional to the number of entries in the list to be summed; a good compensated summation algorithm has an error bound that's independent of the number of entries in the list, and only depends on the range of values in the list and the precision of the floating point types in use.
Because your example doesn't actually show what you think it does. As proof, here's the same code with a different value to "show" that {:.2}
is accurate but round_number
isn't:
fn main() {
println!("{:.2}", 4.3049999999999999); // 4.30 -> accurate
println!("{:.2}", round_number(4.3049999999999999)); // 4.31 -> inaccurate
}
pub fn round_number(nr: f64) -> f64 {
(nr * 100f64).round() / 100f64
}
Rounding is a horrible operation, because any error in the input can be greatly magnified in the output.
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.