You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 4 Next »

Every primitive numeric type in Java has a limited range that is strictly defined by the [JLS 2005]. There are 22 possible narrowing primitive conversions in Java. According to the JLS, Section 5.1.3, "Narrowing Primitive Conversions"

  • 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

Integer Narrowing

The table below 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 type T (precision).

From

To

Description

Possible Resulting Errors

signed integer

integral type T

Keeps only n lower order bits

Magnitude error; sign error

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 narrow data types, the magnitude of the numeric value and the corresponding sign can be affected. Consequently, data can be lost or misinterpreted.

Floating-point Conversion to Integer

Floating-point conversion to an integral type T is a two step procedure. The first step results in a long value if T is long; otherwise, it results in an int value. The second step narrows the int value to T if T is not int using the integer narrowing conversions outlined above. See JLS, Section 5.1.3, "Narrowing Primitive Conversions" for more information.

When converting a floating-point value to an int or long, the following logic is performed: If the value is a NaN, STEP 1 produces an int or long zero value. Otherwise, if the value is not infinity, it is rounded towards zero to an integer value V, and there are two cases:

  • If T is long, and V can be represented as a long, then STEP 1 produces the long value V,
  • or if V can be represented as an int, then STEP 1 produces the int value V.

Otherwise, there must be one of two cases:

  • The value is negative infinity or a value too negative to be represented, and STEP 1 produces MIN_INT or MIN_LONG.
  • The value is positive infinity or a value too positive to be represented, and STEP 1 produces MAX_INT or MAX_LONG.

Narrower primitive types may be cast to wider types without affecting the magnitude of numeric values. See JLS, Section 5.1.2, "Widening Primitive Conversion" for more information. Conversion from int or long to float, or long to double may 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 double to float can also lose information about the overall magnitude of the converted value. See guideline FLP04-J. Use the strictfp modifier for floating point calculation consistency for additional information.

Noncompliant Code Example (Integer Narrowing)

Consider the conversion from an integer to a byte. The higher order bytes of the integer are truncated and the lowest order byte is transferred to the target byte. However, because bytes store signed values, any value outside the range of -128 to +127 results in truncation and data loss. This noncompliant code example shows that an input of 128 yields a result value of -128. Similarly, 129 becomes -127 and so on. This is a consequence of interpreting the bit pattern of low order 8 bits of the input as a signed byte. For example, the binary representation of 129 is 10000001, which, as a byte, is interpreted as -127.

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
  }
}

Compliant Solution (Integer Narrowing)

This compliant solution validates the permissible range before converting the integer to the narrower target type.

class CastAway {
  public static void workWith(int i)
    //check if 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
  } 
}

Noncompliant Code Example (Floating-point Conversion to Integer)

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. In accordance with the language semantics quoted above, the minimum and maximum float values are converted to minimum and maximum int values (0x80000000 and 0x7fffffff), and the resulting short values are the lower 16 bits of these values (0x0000 and 0xffff, respectively). The resulting final values (0 and -1) could be unexpected.

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

Compliant Solution (Floating-point Conversion to Integer)

Perform range checks on both i and j variables before proceeding with the conversions.

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 Conversion to float)

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. Becuase 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, d 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 Conversion to float)

Perform range checks on both i and j variables before proceeding with the conversions.

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

Exceptions

EXP13-EX1: Java's narrowing conversions are both well-defined and portable; knowledgable programmers can intentionally apply such conversions in contexts where their output is both expected and reasonable. Consequently, narrowing conversions are permitted when the code contains comments that document both the use of narrowing conversions and that the potential for truncation has been anticipated. A suitable comment might read: "//Deliberate narrowing cast of i; possible truncation OK"

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. Consequently, data can be misrepresented or interpreted incorrectly.

Guideline

Severity

Likelihood

Remediation Cost

Priority

Level

EXP13-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.

Related Guidelines

C Secure Coding Standard: INT31-C. Ensure that integer conversions do not result in lost or misinterpreted data
C Secure Coding Standard: FLP34-C. Ensure that floating point conversions are within range of the new type
C++ Secure Coding Standard: INT31-CPP. Ensure that integer conversions do not result in lost or misinterpreted data
C++ Secure Coding Standard: FLP34-CPP. Ensure that floating point conversions are within range of the new type
MITRE CWE: CWE-681 "Incorrect Conversion between Numeric Types"
MITRE CWE: CWE-197 "Numeric Truncation Error"

Bibliography

[Harold 1999]
[JLS 2005] Section 5.1.3, "Narrowing Primitive Conversions"


INT00-J. Ensure that integer operations do not result in overflow      Integers (INT)      INT02-J. Do not assume that the remainder operator always returns a non-negative result

  • No labels