I tried the following calculations in jshell of JDK 11:
jshell> 0.1 0.2 == 0.3
$1 ==> false
I can understand this return. After all, neither 0.1, 0.2, or 0.3 can be accurately represented in binary.
But when I switched to the float
instead of the double
, I was surprised to find that the return of jshell was true:
jshell> 0.1f 0.2f == 0.3f
$2 ==> true
This is contrary to my understanding all the time. So I tried to make jshell calculate directly:
jshell> 0.1 0.2
$3 ==> 0.30000000000000004
jshell> 0.1f 0.2f
$4 ==> 0.3
Indeed, if I use the float
data type, it seems to be able to accurately calculate the result to be 0.3.
But why? If double
can't accurately represent and calculate 0.1 0.2, then why float
can?
If there is any error in my test, please point it out. Thank you first!
CodePudding user response:
Even as a float, the number isn't actually 0.3.
public static void main (String[] args) throws java.lang.Exception
{
float a = 0.1f;
float b = 0.2f;
float c = 0.3f;
float d = a b;
System.out.println( new BigDecimal(c) ", " new BigDecimal(d));
}
That puts out.
0.300000011920928955078125, 0.300000011920928955078125
You've found a case where the errors are offsetting.
It would be similar to using a decimal system, and adding 1/3 2/3. 1/3 is approximately 0.333 and 2/3 is approximately 0.667 so when you add the two you get 1.0 even though both of them are approximate.
CodePudding user response:
Each time you perform floating-point operation, the ideal real-number-arithmetic result is rounded to the nearest value representable in the floating-point format, using whichever rounding method applies to the operation (most often round-to-nearest-ties-to-even).
Sometimes a rounding will be in the direction that cancels previous roundings. Sometimes a rounding will be in the direction that exacerbates previous roundings.
Converting the source text 0.1
to double
is a floating-point operation. It produces 0.1000000000000000055511151231257827021181583404541015625, so rounding made the result bigger.
Converting 0.2
to double
produces 0.200000000000000011102230246251565404236316680908203125
, again bigger.
Converting 0.3
to double` produces 0.299999999999999988897769753748434595763683319091796875, so rounding made the result smaller.
Adding the first two, 0.1000000000000000055511151231257827021181583404541015625 and 0.200000000000000011102230246251565404236316680908203125, produces 0.3000000000000000444089209850062616169452667236328125. Here rounding again increased the result.
Converting 0.1f
to float
produces 0.100000001490116119384765625.
Converting 0.2f
to float
produces 0.20000000298023223876953125.
Converting 0.3f
to float
produces 0.300000011920928955078125. In this case, it happens that, because fewer numbers are representable in float
than in double
, the next float
value below 0.3 is farther away from 0.3 than 0.300000011920928955078125 is. So converting 0.3f
to float
rounds up even though converting 0.3
to double
rounds down.
Adding the first two of these float
values, 0.100000001490116119384765625 and 0.20000000298023223876953125, produces 0.300000011920928955078125. Since that is the same as the result of converting 0.3f
, 0.1f 0.2f == 0.3f
evaluates as true.
Another thing to note is that Java’s default display of floating-point numbers produces just enough significant digits to uniquely distinguish the value within its floating-point format. This means, when Java shows “0.3” for a number, it does not mean the floating-point value is 0.3. It means the floating-point value is closer to 0.3 than any other value in that format is, so printing “0.3” is enough to identify it.
This means that when “0.3” is printed for a double
, the actual double
value is 0.299999999999999988897769753748434595763683319091796875, but, when “0.3” is printed for a float
, the actual float
value is 0.300000011920928955078125. Java is not designed to show you the true values of floating-point numbers.