Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do C and Java round floats differently?

Consider the floating point number 0.644696875. Let's convert it to a string with eight decimals using Java and C:

Java

import java.lang.Math;
public class RoundExample{
     public static void main(String[] args){
        System.out.println(String.format("%10.8f",0.644696875));
     }
}

result: 0.64469688

try it yourself: http://tpcg.io/oszC0w

C

#include <stdio.h>

int main()
{
    printf("%10.8f", 0.644696875); //double to string
    return 0;
}

result: 0.64469687

try it yourself: http://tpcg.io/fQqSRF

The Question

Why is the last digit different?

Background

The number 0.644696875 cannot be represented exactly as a machine number. It is represented as the fraction 2903456606016923 / 4503599627370496 which has a value of 0.6446968749999999

This is admittedly an edge case. But I am really curious as to the source of the difference.

Related: https://mathematica.stackexchange.com/questions/204359/is-numberform-double-rounding-numbers

like image 390
gdelfino Avatar asked Sep 03 '19 20:09

gdelfino


People also ask

Why float is not used in Java?

Double and float aren't used to represent values that require a very high precision. So, for instance, to store currency values, it's not a good idea to use double or float; instead Java has a class called BigDecimal that specifies the exact number of digits following the decimal, including rounding up or down.

How do you round up a float in Java?

In order to round float and double numbers in Java, we use the java. lang. Math. round() method.

What is the difference between the float and double data types?

float has 7 decimal digits of precision. double is a 64-bit IEEE 754 double precision Floating Point Number – 1 bit for the sign, 11 bits for the exponent, and 52* bits for the value. double has 15 decimal digits of precision.

Why we use double instead of float?

Double is more precise than float and can store 64 bits, double of the number of bits float can store. Double is more precise and for storing large numbers, we prefer double over float. For example, to store the annual salary of the CEO of a company, double will be a more accurate choice.


1 Answers

Conclusion

The Java specification requires a troublesome double rounding in this situation. The number 0.6446968749999999470645661858725361526012420654296875 is first converted to 0.644696875 and then rounded to 0.64469688.

In contrast, the C implementation simply rounds 0.6446968749999999470645661858725361526012420654296875 directly to eight digits, producing 0.64469687.

Preliminaries

For Double, Java uses IEEE-754 basic 64-bit binary floating-point. In this format, the value nearest the number in the source text, 0.644696875, is 0.6446968749999999470645661858725361526012420654296875, and I believe this is the actual value to be formatted with String.format("%10.8f",0.644696875).1

What the Java Specification Says

The documentation for formatting with the Double type and f format says:

… If the precision is less than the number of digits which would appear after the decimal point in the string returned by Float.toString(float) or Double.toString(double) respectively, then the value will be rounded using the round half up algorithm. Otherwise, zeros may be appended to reach the precision…

Let’s consider “the string returned by … Double.toString(double)”. For the number 0.6446968749999999470645661858725361526012420654296875, this string is “0.644696875”. This is because the Java specification says that toString produces just enough decimal digits to uniquely distinguish the number within the set of Double values, and “0.644696875” has just enough digits in this case.2

That number has nine digits after the decimal point, and "%10.8f" requests eight, so the passage quoted above says “the value” is rounded. Which value does it mean—the actual operand of format, which is 0.6446968749999999470645661858725361526012420654296875, or that string it mentions, “0.644696875”? Since the latter is not a numeric value, I would have expected “the value” to mean the former. However, the second sentence says “Otherwise [that is, if more digits are requested], zeros may be appended…” If we were using the actual operand of format, we would show its digits, not use zeros. But, if we take the string as a numeric value, its decimal representation would have only zeros after the digits shown in it. So it seems this is the interpretation intended, and Java implementations appear to conform to that.

So, to format this number with "%10.8f", we first convert it to 0.644696875 and then round it using the round half up rule, which produces 0.64469688.

This is a bad specification because:

  • It requires two roundings, which can increase the error.
  • The roundings occur in hard-to-predict and hard-to-control places. Some values will be rounded after two decimal places. Some will be rounded after 13. A program cannot easily predict this or adjust for it.

(Also, it is a shame they wrote zeros “may be” appended. Why not “Otherwise, zeros are appended to reach the precision”? With “may”, it seems like they are giving the implementation a choice, although I suspect they meant the “may” is predicated on whether zeros are needed to reach the precision, not on whether the implementor chooses to append them.)

Footnote

1 When 0.644696875 in the source text is converted to Double, I believe the result should be the nearest value representable in the Double format. (I have not located this in the Java documentation, but it fits the Java philosophy of requiring implementations to behave identically, and I suspect the conversion is done in accordance with Double.valueOf(String s), which does require this.) The nearest Double to 0.644696875 is 0.6446968749999999470645661858725361526012420654296875.

2 With fewer digits, the seven-digit 0.64469687 is insufficient because the Double value closest to it is 0.6446968699999999774519210404832847416400909423828125. So eight digits are needed to uniquely distinguish 0.6446968749999999470645661858725361526012420654296875.

like image 122
Eric Postpischil Avatar answered Oct 31 '22 13:10

Eric Postpischil