If I do something like
final float third = 1f / 3f;
System.out.println((third + third + third) == 1.0f);
I get true. Does that mean float can exactly represent 1/3?
If I do something like
final float third = 1f / 3f;
System.out.println((third + third + third) == 1.0f);
I get true. Does that mean float can exactly represent 1/3?
I lost a bet a long time ago when I said "No!" Not only that, but the other party claimed "There is no CS reason that float cannot represent fractional values."
Here is a sample program that explores the issue a bit:
package test;
public class FloatTest
{
public static void main(String[] args)
{
new FloatTest().run();
}
public void run()
{
final float third = 1f / 3f;
final float small = third * .01f;
final float realSmall = third * .0001f;
System.out.println("third: " + third);
System.out.println("small: " + small);
System.out.println("real small: " + realSmall);
System.out.println("Three thirds: " + (third + third + third));
System.out.println("Three small: " + (small + small + small));
System.out.println("Three real small: " + (realSmall + realSmall + realSmall));
}
}
The output is
third: 0.33333334
small: 0.0033333334
real small: 3.3333334E-5
Three thirds: 1.0
Three small: 0.01
Three real small: 1.00000005E-4
The strange results have to do with a couple of things. First, float cannot exactly represent 1/3, any more than 1/3 can be written out with a finite number of decimal digits. In the old days, the result would be .99999999, due to rounding.
In modern times, IEEE 754 has specified how float should be represented and arithmetic handled. In particular, when doing arithmetic, an extra three bits are kept and rounding is performed. That's why the first two results come out exactly right and I lost my bet. However, these extra bits don't guarantee accuracy, and the third result shows.
Here's a decent description of float in general, and the section toward the bottom, "on Rounding" (sic), describes the extra bits: http://pages.cs.wisc.edu/~markhill/cs354/Fall2008/notes/flpt.apprec.html
Bottom line, if you want exact fractional values and to control rounding, use BigDecimal.
A float cannot exactly represent every fraction because the float data type is a single-precision 32-bit IEEE 754 floating point and is subject to IEEE rounding rules. Therefore any thing requiring greater then 32 bits of precision is not representable by a float. There's also the double
which is a double-precision 64-bit IEEE 754 floating point number. Finally, Java has the arbitrary precision BigDecimal class. However it cannot represent every fraction perfectly either, consider the Golden ratio; BigDecimal will throw an exception if you try to calculate
float
cannot represent exactly 1/3. If A/B is a fraction in reduced notation, with A and B both integers, A > 0, and B > 1, the only fractions float
can represent are those where B is a power of 2 up to 2126, and A is less than 224; or other cases where B is a power of 2 greater than 2126, but I won't go into exactly what those are. (I'm not considering B=1; it would take extra work to describe what integers float
is capable of representing, and it's not relevant here.)
Thus, float
cannot represent 1/3. If you compute f=1/3
, the fraction represented by the float
is 11184811/33554432 (33554432 = 225). If you add f+f
, the result is a float
that represents the fraction 11184811/16777216 (16777216 = 224). If you then add this to f
, the exact resulting fraction would be 33554433/33554432. But this fraction cannot be represented exactly in a float
, since it breaks the rule in the first paragraph (33554433 > 224). Thus, the result must be rounded to something that can be represented in a float
, and that something will be 1.
Try this:
final float third = 1f / 3f;
System.out.println(((double)third + (double)third + (double)third) == 1.0);
If third
represented exactly 1/3, then surely casting it to a double
would also represent exactly 1/3, since a double
has more precision than a float
, right? But this displays false
. In fact, the double
on the left side is 1.0000000298023224
if you display it. That is, 33554433/33554432.