'Lossy Conversion' vs. 'Loss of precision' - java

Can someone please explain/link any documentation that can differentiate between
Possible Loss of Precision Error
and
Lossy Conversion.
I cannot understand which error will occur under what circumstance.
Any explanation with examples is deeply appreciated

The difference is the end of the number which gets chopped off:
Lossy conversion returns least-significant bits. It is described in JLS Sec 5.1.3:
A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T. In addition to a possible loss of information about the magnitude of the numeric value, this may cause the sign of the resulting value to differ from the sign of the input value.
It is something like converting an int to a byte: you simply get the 8 least-significant bits in this case:
System.out.println((byte) 258); // 2
Loss of precision returns most-significant bits. It is described in JLS Sec 5.1.2:
A widening primitive conversion from int to float, or from long to float, or from long to double, may result in loss of precision - that is, the result may lose some of the least significant bits of the value.
It is something like storing an int in a float which is too large to be represented accurately
int i = (1 << 24) + 1;
float f = i;
System.out.println((int) f == i); // false, because precision is lost.

You get possible loss of precision error when you try to cast such as double to int. You are trying to convert one primitive to another primitive but there is no enough space and you can get loss of your some bytes.
double x = 10.5; // 8 bytes
int y = x; // 4 bytes ; raises compilation error
You should look for the primitives documentation

Related

Why casting a very large long number to int gives us a strange output (Java)?

I was practicing some castings in Java and I faced a situation for which I couldn't find any answers, anywhere. There are a lot of similar questions with answers, but none gave me an explanation for this particular case.
When I do something like
long l = 165787121844687L;
int i = (int) l;
System.out.println("long: " + l);
System.out.println("after casting to int: " + i);
The output is
long: 165787121844687
after casting to int: 1384219087
This result is very intriguing for me.
I know that the type long is a 64-bit integer, and the type int is a 32-bit integer. I also know that when we cast a larger type to a smaller one, we can lose information. And I know that there is a Math.toIntExact() method that is quite useful.
But what's the explanation for this "1384219087" output? There was loss of data, but why this number? How "165787121844687" became "1384219087"? Why does the code even compile?
That's it. Thanks!
165787121844687L in hex notation = 0000 96C8 5281 81CF
1384219087 in hex notation = 5281 81CF
So the cast truncated the top 32 bits as expected.
32-bits
deleted
▼▼▼▼ ▼▼▼▼
165_787_121_844_687L = 0000 96C8 5281 81CF ➣ 1_384_219_087
64-bit long ▲▲▲▲ ▲▲▲▲ 32-bit int
   32-bits
remaining
If you convert these two numbers to hexadecimal, you get
96C8528181CF
528181CF
See what's happened here?
The Answer by OldProgrammer is correct, and should be accepted. Here is some additional info, and a workaround.
Java spec says so
Why does the code even compile?
When you cast a numeric primitive in Java, you take responsibility for the result including the risk of information loss.
Why? Because the Java spec says so. Always best to read the documentation. Programming by intuition is risky business.
See the Java Language Specification, section 5.1.3. Narrowing Primitive Conversion. To quote (emphasis mine):
A narrowing primitive conversion may lose information …
A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T. In addition to a possible loss of information about the magnitude of the numeric value, this may cause the sign of the resulting value to differ from the sign of the input value.
Math#…Exact…
When you want to be alerted to data loss during conversion from a long to a short, use the Math methods for exactitude. If the operation overflows, an execution is thrown. You can trap for that exception.
try
{
int i = Math.toIntExact( 165_787_121_844_687L ) ; // Convert from a `long` to an `int`.
}
catch ( ArithmeticException e )
{
// … handle conversion operation overflowing an `int` …
}
You will find similar Math#…Exact… methods for absolute value, addition, decrementing, incrementing, multiplying, negating, and subtraction.

Why can you store a long into a float without typecast [duplicate]

This question already has answers here:
Why does Java implicitly (without cast) convert a `long` to a `float`?
(4 answers)
Closed 3 years ago.
Ok so I know float is 32 bit decimal. Double is 64 bit decimal. Long is 64 bit whole number. So why is this value: Float(32 bit) <- Long (64 bit). Obviously not java code but I'm referring to it in java. It automagically casts even though it crunches it into 32 bits and loses precision. So that means precision does not matter. However int <- double does not work unless you explicitly cast the double to an int. It is a smaller size and they are both numbers and both primitives. Is it just the general rule: Decimal Number <- Whole number regardless of bit size. It will just loop back around. Just that rule and everything works among Numbers in java? To show actual code:
long L = 10_000L;
float f = L;
Note that the question is specifically about long to float without an explicit cast, not float to long, not about explicit casting.
Converting long to float is a widening primitive conversion. Java allows it without error because...it's defined that way. From the link:
19 specific conversions on primitive types are called the widening primitive conversions:
byte to short, int, long, float, or double
short to int, long, float, or double
char to int, long, float, or double
int to long, float, or double
long to float or double
...
A widening primitive conversion from int to float, or from long to float, or from long 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).
(My emphasis.)
Why allow it without requiring an explicit cast? In general, only James Gosling and others there at the time can answer that sort of question. For the rest of us, the answer is: Because that's what the specification says.
However, I'll note that even though precision is lost, overall magnitude is not, which I'd wager is why it's allowed. float can hold very large values imprecisely. So for instance:
class Demo {
public static void main(String[] args) {
long l = Long.MAX_VALUE;
float f = l;
System.out.println(l);
System.out.println(f);
}
}
Run that and you get:
9223372036854775807
9.223372E18
9.223372e18 is 9223372000000000000. Compare:
9223372036854775807 - the long value
9223372000000000000 - the float value
This is because float can sacrifice precision for range of value, because it stores a base value that it raises to an exponent. From Wikipedia's article on the IEEE-754 binary32 format (which is what Java's float is):
...an IEEE 754 32-bit base-2 floating-point variable has a maximum value of (2 − 2−23) × 2127 ≈ 3.402823 × 1038...
3.402823 × 1038 is:
340,282,300,000,000,000,000,000,000,000,000,000,000
...but the highest integer it can store such that you can add 1 and not lose precision is just 224-1 (16,777,215). It can store 16,777,216, but not 16,777,217. So 16,777,216 + 1 is still 16,777,216:
class Demo {
public static void main(String[] args) {
float f = 16_777_216f;
System.out.println(String.format("%8.0f", f)); // 16777216
System.out.println(String.format("%8.0f", f + 1)); // 16777216 (still)
}
}
That's because it's now storing the base value divided by 2 with an exponent of 2, so it can't store odd numbers anymore. It gets worse the higher you get, being able to only store multiples of 4, then of 8, then of 16, etc.
Contrast with the maxiumum value of a long, which is 263-1, which is "only":
9,223,372,036,854,775,807
But unlike float, it can represent each and every one of those integers precisely.
There is something like implicit typecasting. Javajava casts it automatically if you will not lose information. From float to long you could lose all behind the floating point so there will be no implicit cast because normally you do not want to lose this information.

typecasting long to float implicitly not narrowing

In java the hierarchy for implicit conversion is
byte -> short -> int -> long -> float -> double
Long can hold 8 bytes of data. Then why it is implicitly typecasted to float which can hold only 4 bytes rather than double which can hold 8 bytes.Why it is not considered as narrowing ?
And which primitive types will be implicitly converted to char ?
It's not about how many bits of data are used. It's about the scale that can be represented.
From JLS section 5.1.2 (widening primitive conversions):
A widening primitive conversion does not lose information about the overall magnitude of a numeric value.
...
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).
The range of long is much smaller than the range of float, but with a fixed precision of 1. The precision of float varies across the range, in terms of absolute value. In other words, no long is outside the range of float, but there are long values which can't be precisely represented as float values.
Moving to double wouldn't help this - there are long values which can't be precisely represented as double, either.
Just because long takes 8 bytes and float is only 4 bytes. How Java accommodates this 8 bytes stuff into 4 bytes? There will be loss of data. Isn’t it?
Conversion from long(8 bytes) to float(4 bytes) is implicit because float save values in the form of exponents.
Float store values in the form of exponents which means 2345678 will be stored as 2.345678E6, where E6 stands for 10 to the power of 6
So when we store a long value which ofcourse cannot be stored in a float, JVM converts it into exponential form and then stores it. Say, if we want to store a long value of 1234567891234 in a float, then it will first be converted into an exponential form like this: 1.234567891234E12 and hence this made it possible to store the value in a float variable which is really done implicitly.
Hold on ! There is still few things that you may have noticed. What about the precision? A Float has precision of 6-7 significant digits after decimal? I am still loosing data.
Here comes one more concept from data storage. The JVM doesn’t cares about loss of precision. It only cares about loss of magnitude. In the above example, 1 is magnitude and the digits after the decimal is the precision.

Why can int/byte/short/long be converted to float/double without typecasting but vice-versa not possible

My code goes like this --
public void abc{
long a=1111;
float b=a; // this works fine even though this is narrowing conversion
long c=b;// this gives compilation error
}
Can you explain why this is happening?
The reason is specified in JLS 5.1.2:
It says that : long to float or double is a widening conversion.
Whereas, float to byte, short, char, int, or long is narrowing conversion.
That's why
float b=a; is working fine in your program , since it is widening conversion.
And
long c=b; is showing compilation error since it is a narrowing conversion.
When you convert from an integral type to a floating point one, it is always clear what you want to do: you change number's representation, but you keep the same number.
Converting from floating point to integral types, on the other hand, has some ambiguity: it is not clear what you would like to do with the fractional part. You may want to
Truncate the fractional part,
Perform mathematical rounding, or
Round the number up.
That's why the language asks you to be specific about what you want to do when converting floating-point numbers to integral types.
Because a long can be represented as a float, but any float cannot be represented as a long. That is mathematic not Java.
Here you could add a cast to force the compilation. Of course, you could lose precision since you are converting a float to a long.
long c = (long) b;
The conversion rules are based on the range of numbers the data types can represent. The range allowed by long is contained in the range allowed by float, so an implicit conversion is allowed despite the obvious loss of precision: long stores 64 bits while float is only 32 so the conversion throws away half of the data.
I Java implicit widening conversions are allowed, but not narrowing. That basically means that it is ok to convert to any datatype that can hold as large or larger values the the original data type without explicitly casting the data. This does however mean that you might lose precision.
ex.
long original = Long.MAX_VALUE-1;
float f = original;
long result = (long) f;
System.err.println(original);
System.err.println(f);
System.err.println(result);
See more at JLS 5.1.2. Widening Primitive Conversion
Take a look at this
byte
1 signed byte (two's complement). Covers values from -128 to 127.
short
2 bytes, signed (two's complement), -32,768 to 32,767
int
4 bytes, signed (two's complement). -2,147,483,648 to 2,147,483,647.
Like all numeric types ints may be cast into other numeric types
(byte, short, long, float, double). When lossy casts are done (e.g.
int to byte) the conversion is done modulo the length of the smaller
type.
long
8 bytes signed (two's complement). Ranges from
-9,223,372,036,854,775,808 to +9,223,372,036,854,775,807.
float
4 bytes, IEEE 754. Covers a range from 1.40129846432481707e-45
to 3.40282346638528860e+38 (positive or negative).
Like all numeric types floats may be cast into other numeric types
(byte, short, long, int, double). When lossy casts to integer types
are done (e.g. float to short) the fractional part is truncated and
the conversion is done modulo the length of the smaller type.
double
8 bytes IEEE 754. Covers a range from 4.94065645841246544e-324d
to 1.79769313486231570e+308d (positive or negative).
Now you can realize if we are going to represent some float and double values in others. part of the original number(float or double) will missing. so casting is not possible in that case

Casting a double to another numeric type

there is something puzzling me and I did not find much information on the VM specs. It's a bit obscure and that'd be nice if someone could explain me.
These few lines of code.....
double myTest = Double.MAX_VALUE;
System.out.println("1. float: " + (float)myTest);
System.out.println("2. int: " + (int)myTest);
System.out.println("3. short: " + (short)myTest);
System.out.println("4. byte: " + (byte)myTest);
..... produce this output:
float: Infinity
int: 2147483647
short: -1
byte: -1
byte, short and int are 8, 16, 32 bit with two's complement. float and double are 32 and 64 bit IEEE 754 (see here).
From my understanding, the max value of a double implies that all the bits of the mantisse (52 bits) are switched to 1. Therefore it's not (very) surprising that a cast to short or to byte returns -1 i.e all bits are switched to 1. It seems that the cast keeps the 'tail' of the double so that it fits into 8 bit byte or 16 bit short.
What surprises me is the cast to int and, to a lesser extent, the cast to float.
How is it possible to get "2. int: 2147483647" which is 0x7FFFFFFF, the maximal value while short and byte 3. and 4. are -1 ?
The cast to float is also weird. If the 32 bits at the 'tail' of myTest were kept, then shouldn't it generate a NaN ?
JLS spells out the rules in section 5.1.3 Narrowing Primitive Conversion. The rules depend on the target type.
float:
A narrowing primitive conversion from double to float is governed by the IEEE 754 rounding rules (§4.2.4). This conversion can lose precision, but also lose range, resulting in a float zero from a nonzero double and a float infinity from a finite double. A double NaN is converted to a float NaN and a double infinity is converted to the same-signed float infinity.
int and long:
one of the following two cases must be true:
...
The value must be too large (a positive value of large magnitude or positive infinity), and the result of the first step is the largest representable value of type int or long.
byte, char and short:
If the target type is byte, char or short, the conversion it two-step. First, the double is converted to long as explained above. Then, the long is converted to the final type as follows:
A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T. In addition to a possible loss of information about the magnitude of the numeric value, this may cause the sign of the resulting value to differ from the sign of the input value.

Categories

Resources