数値型をよりビット幅の小さな型に変換する場合、値が小さな型で表現できる範囲外にあると、データの欠損や誤解釈が発生する可能性がある。そのため、縮小変換を行う前に値の範囲チェックを行い、変換が安全に行えることを保証しなければならない。
Javaには22種類のプリミティブ型の縮小変換が考えられる。Java言語仕様 §5.1.3 「プリミティブ型の縮小変換」には以下のように規定されている。[JLS 2005]
- short から byte または char
- char から byte または short
- int から byte, short, または char
- long から byte, short, char, または int
- float から byte, short, char, int, または long
- double から byte, short, char, int, long, または float
プリミティブ型の縮小変換を行ってもよいのは、値が変換後の小さい型で表現できる範囲に収まっている場合である。
整数の縮小
整数型の範囲については、Java言語仕様 §4.2.1「整数型とその値」に規定されている。「NUM00-J. 整数オーバーフローを検出あるいは防止する」でも整数型について記述している。
以下の表にプリミティブ型の縮小変換のルールを示す。整数型Tについて、nは型Tを表現するのに用いられるビット数(精度)を表す。
From | To | 説明 | 発生しうるエラー |
---|---|---|---|
符号付き整数 | 整数型 T | 下位nビットのみを保持 | データの欠損や誤解釈 |
char | 整数型 T | 下位nビットのみを保持 | 絶対値エラー:charは符号無し16ビットであるが負の値になる |
整数をビット幅の小さなデータ型にキャストすると、数値の絶対値と符号が変化する可能性がある。結果として、データの欠損や誤解釈が起こりうる。
浮動小数点数から整数への変換
浮動小数点数型から整数型 T への変換は、以下の2段階で行われる。
1. 第1段階では、値がNaNである浮動小数点数をintもしくはlongに変換する場合、変換結果はintあるいはlongのゼロとする。そうではなく、値が無限でない場合、ゼロ方向に丸めた整数値Vとする。その後、以下の2つの場合に分かれる。
- Tがlongであり、Vの値がlongで表現できる場合、longの値Vとなる。
- そうでなく、Vがintで表現できる場合、intの値Vとなる。
そうでない場合は、以下のいずれかになる。
- 値が負の無限大であるか、絶対値が大きすぎて表現できない負の数であり、Integer.MIN_VALUE もしくは Long.MIN_VALUEが第1段階の結果となる。
- 値が正の無限大であるか、絶対値が大きすぎて表現できない正の数であり、Integer.MAX_VALUE もしくは Long.MAX_VALUEが第1段階の結果となる。
2. 第2段階では、Tがbyte、charもしくはshortである場合、変換結果は第1段階の結果の値を型Tに縮小変換した結果となる。
詳細は、Java言語仕様§5.1.3「プリミティブ型の縮小変換」を参照。
その他の変換
小さいプリミティブ型の値は、絶対値を変化させることなく、より大きな型にキャストできる。詳しくは Java 言語仕様 §5.1.2「拡大変換」を参照。int や long から float への変換および、long から double への変換は精度の低下を招くことがある(最小位ビットの欠損)。このような精度の低下が発生しても実行時例外は発生しない。
float から double への変換や double からfloat への変換においても、絶対値の情報が欠損することがある。詳しくは「NUM06-J. どのプラットフォームでも一貫した浮動小数点数演算を行うために strictfp 修飾子を使う」を参照。
違反コード (整数の縮小変換)
以下の違反コード例では、範囲チェックを行わずに、int 型の値を byte 型の値に変換している。
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 } }
変換前の値(128)は結果の型で表現できる範囲を越えているため、想定外の値に変換されてしまうだろう。
適合コード (整数の縮小変換)
以下の適合コードでは、小さな型への変換を行う前に、大きな型の値が小さな型で表現できる範囲に収まっていることを検証している。
class CastAway { public static void workWith(int i) { // i が byte の範囲に収まるかどうかをチェック if ((i < Byte.MIN_VALUE) || (i > Byte.MAX_VALUE)) { throw new ArithmeticException("Value is out of range"); } byte b = (byte) i; // b を使った操作 } }
違反コード (浮動小数点数から整数への変換)
以下の違反コードにおける縮小変換では、絶対値の欠損と精度低下の両方が起こっている。
float i = Float.MIN_VALUE; float j = Float.MAX_VALUE; short b = (short) i; short c = (short) j;
float 型の最小値と最大値は int 型の最小値と最大値(それぞれ 0x80000000 と 0x7fffffff)に変換される。これらの値の下位16ビットが変換後の short 型の値となる(0x0000 と 0xffff)。最終的な値(0と-1)は想定外の値であろう。
適合コード (浮動小数点数から整数への変換)
以下の適合コードでは、変数 i と変数 j の値を変換する前に範囲チェックしている。どちらも short 型の範囲には収まらない値なので、このコードでは必ず 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
違反コード (double から float への変換)
以下の違反コードにおける縮小変換では、絶対値の欠損と精度低下の両方が発生している。Double.MAX_VALUE は Float.MAX_VALUE より大きいため、c の値は infinity となる。Double.MIN_VALUE は Float.MIN_VALUE より小さいため、c の値は 0 となる。
double i = Double.MIN_VALUE; double j = Double.MAX_VALUE; float b = (float) i; float c = (float) j;
適合コード (double から float への変換)
この解決法では、変換の前に、i と j の値を範囲チェックしている。どちらも float 型には収まらない値であるため、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
例外
NUM12-EX0: Java の縮小変換の動作は言語仕様上定義されており、可搬性がある。整数型の縮小変換の動作は容易にコードで再現することができる。しかし、浮動小数点数型同士や浮動小数点数型と整数型の間の縮小変換の動作をコードで再現することは難しい。経験豊富なプログラマは、意図的に浮動小数点数型の値を含む縮小変換を行うことがある。コード中に、縮小変換の使用と予想される値の切り捨ての可能性に関して明記するならば、縮小変換を行ってもよい。その場合、次のようなコメントが適切であろう。 "// i の変換は意図的。切り捨てオーケー"
この例外では、範囲チェックせずに整数型同士の縮小変換を行うことは認めない。以下に、範囲チェックをせずに long から int への縮小変換を明示的に行うコード例を示す。
long value = /* initialize */; int i = (int) (value % 0x100000000); // 2^32
縮小変換で通常暗黙的に行われる切り捨てを明示的に行っているため、範囲チェックは不要である。コンパイラが最適化を行うため、パフォーマンス的な悪影響を及ぼすことはない。
他の整数型への変換も同様に行うことができる。
リスク評価
数値データを縮小変換すると、符号や絶対値に関する情報の欠損が発生し、データの誤解釈につながる可能性がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
NUM12-J | 低 | 低 | 中 | P2 | L3 |
自動検出
整数型の縮小変換の自動検出は容易である。それらの変換がプログラマが意図したものかどうかを判断するのは一般に不可能である。ヒューリスティックな警告メッセージは役に立つかもしれない。
関連ガイドライン
The CERT C Secure Coding Standard | INT31-C. Ensure that integer conversions do not result in lost or misinterpreted data |
FLP34-C. Ensure that floating point conversions are within range of the new type | |
The CERT C++ Secure Coding Standard | INT31-CPP. Ensure that integer conversions do not result in lost or misinterpreted data |
FLP34-CPP. Ensure that floating point conversions are within range of the new type | |
ISO/IEC TR 24772:2010 | "Numeric Conversion Errors [FLC]" |
MITRE CWE | CWE-681. Incorrect Conversion between Numeric Types |
CWE-197. Numeric Truncation Error |
参考文献
[Harold 1999] | |
[JLS 2005] | §5.1.3, "Narrowing Primitive Conversions" |
翻訳元
これは以下のページを翻訳したものです。
NUM12-J. Ensure conversions of numeric types to narrower types do not result in lost or misinterpreted data (revision 57)