
When using binary operators with mixed operand sizes, be aware that some of the narrower operands may be promoted to a wider type, to match the type of the other operand. For example in the expression 'a' == 42
, the field 'a'
will be promoted to an int
before the comparison is carried out.
The Java Language Specification [[JLS 05]] section 5.6 "Numeric Promotions" describes numeric promotion as:
- If any of the operands is of a reference type, unboxing conversion is performed. Then:
- If either operand is of type double, the other is converted to double.
- Otherwise, if either operand is of type float, the other is converted to float.
- Otherwise, if either operand is of type long, the other is converted to long.
- Otherwise, both operands are converted to type int.
Widening conversions resulting from integer promotions preserve the overall magnitude of the number. However, promotions in which the operands are converted from a numeric type such as an integer
to a float
or a long
to a double
, are particularly pernicious (see the INT33-J rule for more details regarding this issue). These implicit casts can lead to an undesirable loss in precision.
These conversions can happen with the following operators : multiplicative operators (%, *, /), additive operators (+, -), comparison operators (<, >, <=, >=) and equality (==, !=) and the integer bitwise operators (&, |, ^).
In the following example, a
is promoted to a double
before the +
operator is applied.
int a = some_value; double b = some_other_value; double c = a + b;
As another example, consider:
int a = some_value; char b = some_character; if((a + b) == 0.0f) { //do something }
Here, b
is first converted to int
so that the +
operator can be applied to operands of the same type. The result of (a+b)
is then converted to a float
, and the comparison operator is finally applied.
Noncompliant Code Example
Here is an example of what could happen.
class Test{ public static void main(String[] args){ int big = 1999999999; double big_float = big; float one = 1.0f; System.out.println(big*one); System.out.println(big_float); } }
The output is
2.0E9
1.999999999E9
while the expected output is :
2.0E9
2.0E9
.
big
is first converted to a float
, and loses some precision, which explains the 2.0E9
. However, big_float
doesn't lose any precision since it's a wider type than float
, this is why the two output are different.
Compliant solution
In that particular case, it would be sufficient to use a double
instead of a float
:
class Test{ public static void main(String[] args){ int big = 1999999999; double big_float = big; double one = 1.0d; System.out.println(big*one); System.out.println(big_float); } }
the output is what is expected :
1.999999999E9
1.999999999E9
,
because this time, one
is a double
and doesn't lose precision.
Note : these output were generated on a Java Hotspot(TM) 64-bit server VM (version 1.5.0), and compiled with javac, version 1.5.0.
Risk assessment
Failing to consider integer promotions while dealing with floating point and integer operands together, can result in loss of precision.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
EXP08-J |
low |
probable |
medium |
P4 |
L3 |
References
[[JLS 05]] 5.6 "Numeric Promotions"