This question already has answers here:
Why does Java implicitly (without cast) convert a `long` to a `float`?
(4 answers)
Closed 8 years ago.
if you call the following method of Java
void processIt(long a) {
float b = a; /*do I have loss here*/
}
do I have information loss when I assign the long variable to the float variable?
The Java language Specification says that the float type is a supertype of long.
Do I have information loss when I assign the long variable to the float variable?
Potentially, yes. That should be fairly clear from the fact that long has 64 bits of information, whereas float has only 32.
More specifically, as float values get bigger, the gap between successive values becomes more than 1 - whereas with long, the gap between successive values is always 1.
As an example:
long x = 100000000L;
float f1 = (float) x;
float f2 = (float) (x + 1);
System.out.println(f1 == f2); // true
In other words, two different long values have the same nearest representation in float.
This isn't just true of float though - it can happen with double too. In that case the numbers have to be bigger (as double has more precision) but it's still potentially lossy.
Again, it's reasonably easy to see that it has to be lossy - even though both long and double are represented in 64 bits, there are obviously double values which can't be represented as long values (trivially, 0.5 is one such) which means there must be some long values which aren't exactly representable as double values.
Yes, this is possible: if only for the reason that float has too few (typically 6-7) significant digits to deal with all possible numbers that long can represent (19 significant digits). This is in part due to the fact that float has only 32 bits of storage, and long has 64 (the other part is float's storage format † ). As per the JLS:
A widening conversion of an int or a long value to float, or of a long value to double, may result in loss of precision - that is, the result may lose some of the least significant bits of the value. In this case, the resulting floating-point value will be a correctly rounded version of the integer value, using IEEE 754 round-to-nearest mode (§4.2.4).
By example:
long i = 1000000001; // 10 significant digits
float f = i;
System.out.printf(" %d %n %.1f", i, f);
This prints (with the difference highlighted):
1000000001
1000000000.0
~ ← lost the number 1
It is worth noting this is also the case with int to float and long to double (as per that quote). In fact the only integer → floating point conversion that won't lose precision is int to double.
~~~~~~
† I say in part as this is also true for int widening to float which can also lose precision, despite both int and float having 32-bits. The same sample above but with int i has the same result as printed. This is unsurprising once you consider the way that float is structured; it uses some of the 32-bits to store the mantissa, or significand, so cannot represent all integer numbers in the same range as that of int.
Yes you will, for example...
public static void main(String[] args) {
long g = 2;
g <<= 48;
g++;
System.out.println(g);
float f = (float) g;
System.out.println(f);
long a = (long) f;
System.out.println(a);
}
... prints...
562949953421313
5.6294995E14
562949953421312
Related
Using this snippet:
public static void main(String[] args){
int i = XXX;
System.out.println( (int) ( (float) i ) );
}
If int i = 1234; then the output is 1234
If int i = Integer.MAX_VALUE; then the output is equal to Integer.MAX_VALUE
However, if int i = 1234567990;, then the output is 1234567936, which is not equal to i.
And if int i = 1235567990;, then the output is 1235568000, which is also not equal to i.
How does this casting conversion math work?
This is entirely normal for floating point maths. Both int and float are 32-bit values. float only has about 7 significant digits of precision, because it uses some of the bits for scale. As you get to large values, "adjacent" float values (i.e. going from one precisely-representable value to the next one up) are more than 1 apart - so any integer between those two adjacent values can't be represented precisely as a float.
Another way of looking at this is a version of the pigeonhole principle:
Both float and int have 2^32 possible bit patterns
There's a valid mapping from every int to a float value
Every bit pattern is a valid int, so there are 2^32 possible integer values
float also contains the value 1.5 (and many other non-integers, but we only need one of them to prove the point)
Therefore at least two int values must map to the same float, which means the mapping cannot be reversible in every case
Note that if you use double, you're fine - every int can be cast to double and then cast back to int, and you'll get the original value. However, using double and long (instead of int) you get the same problem, for the same reason, but using 64 bits instead of 32.
How does this casting conversion math work?
Casting an int to a float works the same way any float operation works: Do the math (which in this case is nothing - just take the int as is), then convert it to the 'nearest representable float' - that float which is closer to the result of the calculation than any other.
Why is it 'lossy'? See the other answer.
What's the math behind which floats are representable? Wikipedia's page on IEEE754 floating point representation explains that.
I have to compute 11^16 for a project at my Uni. Somehow Math.pow(11,16) computes a solution exactly 1 less than WolframAlpha or my other computation method.
My code is:
public class Test {
public static void main(String args[]) {
long a = 11;
long b = 16;
System.out.println("" + (long)Math.pow(a, b));
System.out.println("" + squareAndMultiply(a, b));
}
public static long squareAndMultiply(long b, long e){
long result = 1;
long sq = b;
while(e>0){
if(e%2==1){
result *= sq;
}
sq = sq * sq;
e /= 2;
}
return result;
}
}
The result from the code is:
math.pow(11,16):
45949729863572160
squareAndMultiply(11,16):
45949729863572161
With floating-point arithmetic, you're in that gray zone where the precision of a double is less than that of a long (even if the range of a double is much bigger).
A double has 53 bits of precision, whereas a long can devote all 64 bits to precision. When you're dealing with values as high as 1116, the difference between one double value and the next one up becomes noticeable.
Java has a built-in method Math.ulp ("unit in last place") that effectively gives the difference in values between consecutive representable values. (There's a double version and a float version.)
System.out.println(Math.ulp(Math.pow(11, 16)));
8.0
That means the least possible double value greater than 45949729863572160 is 45949729863572168.
The long value 45949729863572161 is correct, but the value you're getting with Math.pow, 45949729863572160, is as close as a double can get to the true answer, given its limited (but still large) precision.
Casting to a long makes no difference, because Math.pow already computes the result as a double, so the answer is off by one already. Your long method of computing the value is correct.
If you're computing values that would overflow long, then instead of using double, you can use BigDecimal, which has its own pow method to retain a precision of 1.0.
The root cause of this discrepancy is loss of precision because of Double precision numbers are accurate up to sixteen decimal places.
One way to demonstrate is this example.
System.out.println((double)999999999999999999L);
outputs:
1.0E18
The output of Math.pow(11, 16) is 4.594972986357216E16, which on casting to long gets converted into 45949729863572160.
If you are interested more in learning about the loss of precision, you can check this.
All integer literals are treated as int in java and floating point literals are treated as double in java.
Then why does
byte b =10;
does not give any error but
float f = 10.0;
gives a loss of precision error when in both cases down-casting takes place?
In the case of int to byte, there's no real concern about a loss of precision, because both types have the same degree of granularity. You'll get an error if you try to convert a literal with a value outside the range of byte to byte. (The error message given in that case is slightly misleading.)
In the case of double to float, you can have a constant value which is in the right range, but still lose precision. In your specific case of 10.0, the value can be represented exactly in both float and double, but that's not the case in general.
As an example of that, consider this:
float f = (float) 10.1; // Or float f = 10.1f;
double d = 10.1;
System.out.println(f == d); // Prints false
That's because precision is being lost in the conversion from double tofloat - neither type can represent 10.1 exactly, but double gets close to it than float does. The == operator will mean f is converted back to a double, with a different value to d.
This is the snippet of code in Java:
int i = 1234567890;
float f = i;
System.out.println(i - (int)f);
Why is that the output is not equal to 0? It performs widening, so it is not supposed to loose data. Then you just truncate the value.
Best regards
See the difference
int i = 1234567890;
float f = i;
System.out.println(i - f);
System.out.println((int)f);
System.out.println(f);
System.out.println(i-(int)f);
Ouput:
0.0
1234567936
1.23456794E9
-46
Your mistake is here:
It performs widening, so it is not supposed to loose data.
This statement is wrong. Widening does not mean that you do not lose data.
From the Java specification:
Widening primitive conversions do not lose information about the overall magnitude of a numeric value.
Conversion of an int or a long value to float, or of a long value to double, may result in loss of precision - that is, the result may lose some of the least significant bits of the value. In this case, the resulting floating-point value will be a correctly rounded version of the integer value, using IEEE 754 round-to-nearest mode (§4.2.4).
Emphasis mine.
The specification clearly states that the magnitude is not lost, but precision can be lost.
The word widening refers not to the precision of a data type, but to its range. Floats are wider than ints because they have a larger range.
int
4 bytes, signed (two's complement). -2,147,483,648 to 2,147,483,647.
float
4 bytes, IEEE 754. Covers a range from 1.40129846432481707e-45 to 3.40282346638528860e+38 (positive or negative).
As you can see, float has a larger range. However some integers cannot be represented exactly as floats. This representation error is what causes your result to differ from 0. The actual value stored in f is 1234567936.
You'd be shocked to find out that even this can be true:
(f==(f+1))==true
given f is a float large enough...
int i = 1234567890;
float f = i;
System.out.println((int)f);
System.out.println(i);
System.out.println(i - (int)f);
output is:
1234567936
1234567890
-46
see the difference.
compare integer float have larger value.
Widening can lose precision. An int has 32-bits of precision where as a float has a 25-bits of precision (a 24-bit mantissa and an implied top bit). Java considers float to be wider than the 64-bit long which is a bit mad IMHO
With an 8-bit difference in precision, the error can be up to about +/-64 as it rounds the int value to the nearest float representation.
int err = 0;
for (int i = Integer.MAX_VALUE; i > Integer.MAX_VALUE / 2; i--) {
int err2 = (int) (float) i - i;
if (Math.abs(err2) > err) {
System.out.println(i + ": " + err2);
err = Math.abs(err2);
}
}
prints
... deleted ...
2147483584: 63
2147483456: -64
In Java, we can convert an int to float implicitly, which may result in loss of precision as shown in the example code below.
public class Test {
public static void main(String [] args) {
int intVal = 2147483647;
System.out.println("integer value is " + intVal);
double doubleVal = intVal;
System.out.println("double value is " + doubleVal);
float floatVal = intVal;
System.out.println("float value is " + floatVal);
}
}
The output is
integer value is 2147483647
double value is 2.147483647E9
float value is 2.14748365E9
What is the reason behind allowing implicit conversion of int to float, when there is a loss of precision?
You are probably wondering:
Why is this an implicit conversion when there is a loss of information? Shouldn't this be an explicit conversion?
And you of course have a good point. But the language designers decided that if the target type has a range large enough then an implicit conversion is allowed, even though there may be a loss of precision. Note that it is the range that is important, not the precision. A float has a greater range than an int, so it is an implicit conversion.
The Java specification says the following:
A widening conversion of an int or a long value to float, or of a long value to double, may result in loss of precision - that is, the result may lose some of the least significant bits of the value. In this case, the resulting floating-point value will be a correctly rounded version of the integer value, using IEEE 754 round-to-nearest mode.
Converting an integer type to a floating point type that uses the same number of bits may result in a loss of precision, but will be done automatically.
"Loss of precision" means that some of the less significant digits may become zeros, but the most important digits and the size of the number will remain. Recall that float has only about seven decimal digits of precision. For example, converting the int 123456789 to a float 123456700.0 shows a loss of precision.