Conversions of numeric types to narrower types can result in lost or misinterpreted data if the value of the wider type is outside the range of values of the narrower type. Consequently, all narrowing conversions must be guaranteed safe by range-checking the value before conversion.

Java provides 22 possible narrowing primitive conversions. According to The Java Languagee Specification (JLS), §5.1.3, "Narrowing Primitive Conversions" [JLS 2015]:

  • short to byte or char
  • char to byte or short
  • int to byte, short, or char
  • long to byte, short, char, or int
  • float to byte, short, char, int, or long
  • double to byte, short, char, int, long, or float

Narrowing primitive conversions are allowed in cases where the value of the wider type is within the range of the narrower type.

Integer Narrowing

Integer type ranges are defined by the JLS, §4.2.1, "Integral Types and Values" [JLS 2015], and are also described in NUM00-J. Detect or prevent integer overflow.

The following table presents the rules for narrowing primitive conversions of integer types. In the table, for an integer type T, n represents the number of bits used to represent the resulting type T (precision).

From

To

Description

Possible Resulting Errors

Signed integer

Integral type T

Keeps only n lower-order bits

Lost or misinterpreted data

char

Integral type T

Keeps only n lower-order bits

Magnitude error; negative number even though char is 16-bit unsigned

When integers are cast to narrower data types, the magnitude of the numeric value and the corresponding sign can be affected. Consequently, data can be lost or misinterpreted.

Floating-Point to Integer Conversion

Floating-point conversion to an integral type T is a two-step procedure:

1. When converting a floating-point value to an int or long and the value is a NaN, a zero value is produced. Otherwise, if the value is not infinity, it is rounded toward zero to an integer value V:

  • If T is long and V can be represented as a long, the long value V is produced.
  • If V can be represented as an int, then the int value V is produced.

Otherwise,

  • The value is negative infinity or a value too negative to be represented, and Integer.MIN_VALUE or Long.MIN_VALUE is produced.
  • The value is positive infinity or a value too positive to be represented, and Integer.MAX_VALUE or Long.MAX_VALUE is produced.

2. If T is byte, char, or short, the result of the conversion is the result of a narrowing conversion to type T of the result of the first step

See the JLS, §5.1.3, "Narrowing Primitive Conversions" [JLS 2005], for more information.

Other Conversions

Narrower primitive types can be cast to wider types without affecting the magnitude of numeric values (see the JLS, §5.1.2, Widening Primitive Conversion" [JLS 2005]), for more information). Conversion from int or long to float or from long to double can lead to loss of precision (loss of least significant bits). No runtime exception occurs despite this loss.

Note that conversions from float to double or from double to float can also lose information about the overall magnitude of the converted value (see NUM53-J. Use the strictfp modifier for floating-point calculation consistency across platforms for additional information).

Noncompliant Code Example (Integer Narrowing)

In this noncompliant code example, a value of type int is converted to a value of type byte without range checking:

class CastAway {
  public static void main(String[] args) {
    int i = 128;
    workWith(i);
  }

  public static void workWith(int i) {
    byte b = (byte) i;  // b has value -128
    // Work with b
  }
}

The resulting value may be unexpected because the initial value (128) is outside of the range of the resulting type.

Compliant Solution (Integer Narrowing)

This compliant solution validates that the value stored in the wider integer type is within the range of the narrower type before converting to the narrower type. It throws an ArithmeticException if the value is out of range.

class CastAway {
  public static void workWith(int i) {
    // Check whether i is within byte range
    if ((i < Byte.MIN_VALUE) || (i > Byte.MAX_VALUE)) { 
      throw new ArithmeticException("Value is out of range");
    }

    byte b = (byte) i;
    // Work with b
  } 
}

 Alternatively, the workWith() method can explicitly narrow when the programmer's intent is to truncate the value:

class CastAway {
   public static void workWith(int i) { 
     byte b = (byte)(i % 0x100); // 2^8;
     // Work with b
   }
}

Range-checking is unnecessary because the truncation that is normally implicit in a narrowing conversion is made explicit. The compiler will optimize the operation away, and for that reason, no performance penalty is incurred. Similar operations may be used for converting to other integral types.

Noncompliant Code Example (Floating-Point to Integer Conversion)

The narrowing primitive conversions in this noncompliant code example suffer from loss in the magnitude of the numeric value as well as a loss of precision:

float i = Float.MIN_VALUE;
float j = Float.MAX_VALUE;
short b = (short) i;
short c = (short) j;

The minimum and maximum float values are converted to 0 and maximum int values (0x7fffffff respectively). The resulting short values are 0 and the lower 16 bits of this value (0xffff). The resulting final values (0 and −1) might be unexpected.

Compliant Solution (Floating-Point to Integer Conversion)

This compliant solution range-checks both the i and j variables before converting to the resulting integer type. Because the maximum value is out of the valid range for a short, this code will always throw an ArithmeticException.

float i = Float.MIN_VALUE;
float j = Float.MAX_VALUE;
if ((i < Short.MIN_VALUE) || (i > Short.MAX_VALUE) ||
    (j < Short.MIN_VALUE) || (j > Short.MAX_VALUE)) {
  throw new ArithmeticException ("Value is out of range");
}

short b = (short) i;
short c = (short) j;
// Other operations

Noncompliant Code Example (double to float Conversion)

The narrowing primitive conversions in this noncompliant code example suffer from a loss in the magnitude of the numeric value as well as a loss of precision. Because Double.MAX_VALUE is larger than Float.MAX_VALUE, c receives the value infinity, and because Double.MIN_VALUE is smaller than Float.MIN_VALUE, b receives the value 0.

double i = Double.MIN_VALUE;
double j = Double.MAX_VALUE;
float b = (float) i;
float c = (float) j;

Compliant Solution (double to float Conversion)

This compliant solution performs range checks on both i and j before proceeding with the conversions. Because both values are out of the valid range for a float, this code will always throw an ArithmeticException.

double i = Double.MIN_VALUE;
double j = Double.MAX_VALUE;
if ((i < Float.MIN_VALUE) || (i > Float.MAX_VALUE) ||
    (j < Float.MIN_VALUE) || (j > Float.MAX_VALUE)) {
  throw new ArithmeticException ("Value is out of range");
}

float b = (float) i;
float c = (float) j;
// Other operations

Risk Assessment

Casting a numeric value to a narrower type can result in information loss related to the sign and magnitude of the numeric value. As a result, data can be misrepresented or interpreted incorrectly.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

NUM12-J

Low

Unlikely

Medium

P2

L3

Automated Detection

Automated detection of narrowing conversions on integral types is straightforward. Determining whether such conversions correctly reflect the intent of the programmer is infeasible in the general case. Heuristic warnings could be useful.

ToolVersionCheckerDescription
CodeSonar
8.1p0

JAVA.MATH.APPROX.E
JAVA.MATH.APPROX.PI
JAVA.CAST.FTRUNC
JAVA.ARITH.FPEQUAL

Approximate e Constant (Java)
Approximate pi Constant (Java)
Cast: Integer to Floating Point (Java)
Floating Point Equality (Java)

Parasoft Jtest
2023.1
CERT.NUM12.CLPDo not cast primitive data types to lower precision

Related Guidelines

Bibliography



6 Comments

  1. There are some inconsistencies between this guideline , the JLS,and NUM04-J. Use the strictfp modifier for floating point calculation consistency that need to be fixed.

    This guideline and the JLS says that:

    Note that conversions from float to double or double to float can also lose information about the overall magnitude of the converted value.

    NUM04-J. Use the strictfp modifier for floating point calculation consistency says that these conversions can lead to a loss of magnitude, precision, or both.

    Dean is working on resolving.

    1. JLS 4.2.3 very clearly states that the "Each extended-exponent value set has a larger range of exponent values than the corresponding standard value set, but does not have more precision." So that resolves that question. I note that the x86 FP registers actually have both extended exponent ranges AND more mantissa bits than either float or double (sign bit, 63 mantissa bits, and 16 bit exponent, IIRC). Is this section of the JLS actually enforced in practice? The obvious thing for a JIT to do for performance (in the absence of strictfp, of course) would result in intermediate values with both additional range and additional precision, but that would violate this section.

      What's the real truth?

      [edit a bit later] The answer is that the JLS speaks truth. I'd forgotten that the Intel chips support a mode in which they automatically truncate the mantissa in the FP registers to either 24 or 53 bits without loss of performance. And that latter clause allows conformance with the JLS. More accurately, the "without loss of performance" part allowed the JLS to be specified the way it is with no negative impact on x86 family platforms.

      I have fixed NUM04-J to be consistent with this guideline and JLS 4.2.3.

  2. I have a question about the noncompliant case in `Noncompliant Code Example (Floating-Point to Integer Conversion)`.

    When converting Float.MIN_VALUE to int type, I get the value "0" but not "0x80000000". So I wonder if it is wrong in the example code, should it be "float i = -Float.MAX_VALUE;" in the first line?

    1. Sigh. Simple error in the code description. Float.MIN_VALUE is not negative, it is 10 raised to the most negative exponent; eg. really close to 0.  I've fixed it.

      1. I have another question about the reason why `short b = (short) i;` is a violation of this rule.  The reason is the loss of precision but not the magnitude of the numeric value, right? As `i` is not beyond the range of short type.

        So I get the conclusion here:

        The conversion from any float type value with decimal part to an integer type value will lead to the violation of this rule.

        Is this understanding correct?

        1. No that line violates the rule. The violation is casting i to a short, not assigning it to b.  Remember that the cast of i to a short involves two steps (1) first casting b to an int, and then (2) casting it to a short.  The first cast loses precision and magnitude, since an int cannot represent the same magnitude as a float. The second cast loses no precision (because it is integer-to-integer) but it loses more magnitude.

          This rule does not speak to the loss of precision, but it is fair to say that if the decimal part of the number is important to you, then don't cast your float to any integer type (smile)