Perversely, you see what you're seeing because double
is more precise than float
, rather than in spite of it. What you're seeing is a result of how floating point numbers are converted to strings by the default toString
operation of each class, which depends in part on the precision of the format of the floating point number.
TL;DR: The default "to string" operations for float
and double
each create strings that only contain as many digits as necessary to uniquely identify the value in the float
or double
relative to the adjacent representable values in that same format, rather than outputting all digits of the value. Since double
can represent more values than float
, its string has to be more precise.
Details:
Let's start here: The actual value you get from 1.35f - 0.00026f
isn't 1.34974
, it's 1.34974002838134765625
(since, as you probably know, not all floating point numbers can be represented in either the float
or the double
format). The double
equivalent 1.35 - 0.00026
gives you a more accurate value: 1.349740000000000161861635206150822341442108154296875
. (I used baseconvert.com's IEEE-754 converter for these, double-checking the 32-bit results against h-schmidt.net and by using the code below.) Let's see that by using BigDecimal
to format those values to strings rather than toString
(live copy):
import java.math.BigDecimal;
public class Example {
private static String format(float value) {
return new BigDecimal(value).toPlainString();
}
private static String format(double value) {
return new BigDecimal(value).toPlainString();
}
public static void main(String[] args) throws Exception {
float f = 1.35f - 0.00026f;
double d = 1.35 - 0.00026;
System.out.println("Float: " + format(f));
// "Float: 1.34974002838134765625"
System.out.println("Double: " + format(d));
// "Double: 1.349740000000000161861635206150822341442108154296875"
}
}
In fact, neither 1.35
nor 0.00026
is precisely held by either float
or double
either, so the imprecision kicks in even before the subtraction.
Okay, so why is float
's toString
giving you just "1.34974"
when double
's toString
gives you "1.3497400000000002"
? Because the toString
methods of both classes (float
, double
) produce the shortest (usually) output for a value that differentiates it from the representable values adjacent to it on either side.
With float
, because float
is quite imprecise, the short string "1.34974"
is enough to identify the value the float
actually contains (and not one of the values next to it). That is, even though the actual value is 1.34974002838134765625
, not all of those digits are needed to differentiate it from other representable float
values, all you need in the realm of float
is "1.34974"
. Float.parseFloat("1.34974")
gives you the float
value 1.34974002838134765625
again.
Not so for double
, though, because double
has more representable values than float
does, so the string "1.34974"
doesn't uniquely identify the double
value 1.349740000000000161861635206150822341442108154296875
. In fact, if you parse "1.34974"
as a double
, you get 1.3497399999999999398170302811195142567157745361328125
instead. So it has to include more digits to differentiate the double
value you're making into a string from other double
values — because double
is more precise than float
.