Numeric promotions are used to convert the operands of a numeric operator to a common type so that an operation can be performed. When using arithmetic operators with mixed operand sizes, narrower operands are promoted to the type of the wider operand.

Promotion Rules

The Java Language Specification (JLS), §5.6, "Numeric Promotions" [JLS 2013], describes numeric promotion as the following:

  1. If any of the operands is of a reference type, unboxing conversion is performed.
  2. If either operand is of type double, the other is converted to double.
  3. Otherwise, if either operand is of type float, the other is converted to float.
  4. Otherwise, if either operand is of type long, the other is converted to long.
  5. 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 an int to a float or from a long to a double can cause a loss of precision. (See NUM13-J. Avoid loss of precision when converting primitive integers to floating-point for more details.)

These conversions can occur when using the multiplicative operators (%, *, /), additive operators (+, -), comparison operators (<, >, <=, >=), equality operators (==, !=), and the integer bitwise operators (&, |, ^).

Examples

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;

In the following program fragment, b is first converted to int so that the + operator can be applied to operands of the same type:

int a = some_value;
char b = some_character;

if ((a + b) > 1.1f) {
  // Do something
}

The result of (a + b) is then converted to a float, and the comparison operator is finally applied. 

Compound Operators

Type coercion may occur when compound expressions are used with mixed operand types. Examples of compound assignment operators are +=, -=, *=, /=, &=, ^=, %=, <<=, >>=, >>>=, and |=.

According to the JLS, §15.26.2, "Compound Assignment Operators" [JLS 2013],

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.

That is, the compound assignment expression implicitly casts the resulting computation to the type of the left-hand operand.

When the operands are different types, multiple conversions can occur. For example, when E1 is an int and E2 is either a long, a float, or a double, then E1 is widened from type int to the type of E2 (before the "op"), followed by a narrowing conversion from the type of E2 back to type int (after the "op" but before the assignment).

Noncompliant Code Example (Multiplication)

In this noncompliant code example, a variable of type int (big) is multiplied by a value of type float (one)

int big = 1999999999;
float one = 1.0f;
// Binary operation, loses precision because of implicit cast
System.out.println(big * one); 

In this case, numeric promotions require that big be promoted to the type float before the multiplication occurs, resulting in loss of precision. (See NUM13-J. Avoid loss of precision when converting primitive integers to floating-point.). This code outputs 2.0E9 rather than 1.999999999E9.

Compliant Solution (Multiplication)

This compliant solution uses the double type, instead of float, as a safer means of handling the widening primitive conversion resulting from integer promotion:

int big = 1999999999;
double one = 1.0d; // Double instead of float
System.out.println(big * one); 

This solution produces the expected output of 1.999999999E9, which is the value obtained when an int is assigned (implicitly cast) to a double.

See also NUM50-J. Convert integers to floating point for floating-point operations for more information about mixing integer and floating-point arithmetic.

Noncompliant Code Example (Left Shift)

This noncompliant code example shows integer promotion resulting from the use of the bitwise OR operator. 

byte[] b = new byte[4];
int result = 0;
for (int i = 0; i < 4; i++) {
  result = (result << 8) | b[i];
} 

Each byte array element is sign-extended to 32 bits before it is used as an operand. If it originally contained the value 0xff, it would contain 0xffffffff [Findbugs 2008]. This causes result to contain a value other than the concatenation of the four array elements.

Compliant Solution (Left Shift)

This compliant solution masks off the upper 24 bits of the byte array element to achieve the intended result:

byte[] b = new byte[4];
int result = 0;
for (int i = 0; i < 4; i++) {
  result = (result << 8) | (b[i] & 0xff);
} 

Noncompliant Code Example (Compound Addition and Assignment)

This noncompliant code example performs a compound assignment operation. 

int x = 2147483642; // 0x7ffffffa
x += 1.0f;          // x contains 2147483647 (0x7fffffff) 
                    // after the computation 

The compound operation involves an int value that contains too many significant bits to fit in the 23-bit mantissa of a Java float, causing the widening conversion from int to float to lose precision. The resulting value is frequently unexpected.

Compliant Solution (Compound Addition and Assignment)

For defensive programming purposes, avoid using any of the compound assignment operators on variables of types byte, short, or char. Also, refrain from using a wider operand on the right-hand side. In this compliant solution, all operands are of the Java type double.

double x = 2147483642; // 0x7ffffffa
x += 1.0; // x contains 2147483643.0 (0x7ffffffb.0) as expected

Noncompliant Code Example (Compound Bit Shift and Assignment)

This noncompliant code example uses a compound right-shift operator for shifting the value of i, bit. 

short i = -1;
i >>>= 1;

Unfortunately, the value of i remains the same. The value of i is first promoted to an int. This is a widening primitive conversion, so no data is lost. As a short, -1 is represented as 0xffff. The conversion to int results in the value 0xffffffff, which is right-shifted by 1 bit to yield 0x7fffffff. To store the value back into the short variable i, Java performs an implicit narrowing conversion, discarding the 16 higher-order bits. The final result is again 0xffff, or -1.

Compliant Solution (Compound Bit Shift and Assignment)

This compliant solution applies the compound assignment operator to an int, which does not require widening and subsequent narrowing. Consequently, i gets the value 0x7fffffff.

int i = -1;
i >>>= 1;

Applicability

Failing to consider integer promotions when dealing with floating-point and integer operands can result in loss of precision.

Automated Detection

ToolVersionCheckerDescription
SonarQube9.9S3034 

 

Bibliography

[Bloch 2005]

Puzzle 9, "Tweedledum"
Puzzle 31, "Ghost of Looper"

[Findbugs 2008]

"BIT: Bitwise OR of Signed Byte Value"

[JLS 2013]

§4.2.2, "Integer Operations"
§5.6, "Numeric Promotions"
§15.26.2, "Compound Assignment Operators"

 


3 Comments

  1. This recommendation can refer to EXP04-J and EXP05-J as other examples of problems that can arise due to implicit integer promotions.

  2. Please add a declaration for the array b in the left shift examples, so we can see clearly that it is a byte array.

    int result = 0;
    for (int i = 0; i < 4; i++) 
      result = ((result << 8) | b[i]);