NUM12-J. 数値型の縮小変換時にデータの欠損や誤解釈を引き起こさない
数値型をよりビット幅の小さな型に変換する場合、値が小さな型で表現できる範囲外にあると、データの欠損や誤解釈が発生する可能性がある。そのため、縮小変換を行う前に値の範囲チェックを行い、変換が安全に行えることを保証しなければならない。
Javaには22種類の「プリミティブ型の縮小変換」が考えられる。『Java言語仕様』§5.1.3「プリミティブ型の縮小変換」には以下のように規定されている。[JLS 2015]
shortからbyteまたはcharcharからbyteまたはshortintからbyte,short, またはcharlongからbyte,short,char, またはintfloatからbyte,short,char,int, またはlongdoubleからbyte,short,char,int,long, またはfloat
プリミティブ型の縮小変換を行ってもよいのは、値が変換後の小さい型で表現できる範囲に収まっている場合である。
整数の縮小
整数型の範囲については、Java言語仕様§4.2.1「整数型とその値」 [JLS 2015] に規定されている。「NUM00-J. 整数オーバーフローを検出あるいは防止する」でも整数型について記述している。
以下の表にプリミティブ型の縮小変換のルールを示す。整数型Tについて、nは型Tを表現するのに用いられるビット数 (精度) を表す。
|
From |
To |
説明 |
発生しうるエラー |
|---|---|---|---|
|
符号付き整数 |
整数型 |
下位 |
データの欠損や誤解釈 |
|
|
整数型 |
下位 |
絶対値エラー: |
整数をビット幅の小さなデータ型にキャストすると、数値の絶対値と符号が変化する可能性がある。結果として、データの欠損や誤解釈が起こりうる。
浮動小数点数から整数への変換
浮動小数点数型から整数型Tへの変換は、以下の2段階で行われる。
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. Tがbyte、charもしくはshortである場合、変換結果は第1段階の結果の値を型Tに縮小変換した結果となる。
詳細は、Java言語仕様§5.1.3「プリミティブ型の縮小変換」[JLS 2005]を参照。
その他の変換
小さいプリミティブ型の値は、絶対値を変化させることなく、より大きな型にキャストできる (詳しくは Java 言語仕様 §5.1.2「プリミティブ型の拡大変換」[JLS 2005] を参照)。int や long から float への変換および、long から double への変換は精度の低下を招くことがある (最下位ビットの欠損)。このような精度の低下が発生しても実行時例外は発生しない。
float から double への変換や double から float への変換においても、絶対値の情報が欠損することがある (詳しくは「NUM53-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 の値は -128 になる
// b を使った操作
}
}
変換前の値 (128) は結果の型で表現できる範囲を越えているため、想定外の値に変換されてしまうだろう。
適合コード (整数の縮小変換)
以下の適合コードでは、小さな型への変換を行う前に、大きな型の値が小さな型で表現できる範囲に収まっていることを検証している。値が範囲外の場合、ArithmeticException 例外がスローされる。
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 を使った操作
}
}
あるいは、プログラマが意図して値を切り捨てたいなら、workWith() メソッドで明示的に縮小変換してもよい。
class CastAway {
public static void workWith(int i) {
byte b = (byte)(i % 0x100); // 2^8;
// b を使った操作
}
}
縮小変換で通常暗黙的に行われる切り捨てを明示的に行っているため、範囲チェックは不要である。コンパイラが最適化を行うため、パフォーマンス的な悪影響を及ぼすことはない。他の整数型への変換も同様に行うことができる。
違反コード (浮動小数点数から整数への変換)
以下の違反コードにおける縮小変換では、絶対値の欠損と精度低下の両方が起こっている。
float i = Float.MIN_VALUE;
float j = Float.MAX_VALUE;
short b = (short) i;
short c = (short) j;
float 型の最小値と最大値は int 型の 0 と最大値 (0x7fffffff) に変換される。0 とこの値の下位16ビット (0xffff) が変換後の short 型の値となる。最終的な値 (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;
// 他の演算
違反コード (double から float への変換)
以下の違反コードにおける縮小変換では、絶対値の欠損と精度低下の両方が発生している。Double.MAX_VALUE は Float.MAX_VALUE より大きいため、c の値は infinity となる。Double.MIN_VALUE は Float.MIN_VALUE より小さいため、b の値は 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;
// 他の演算
リスク評価
数値データを縮小変換すると、符号や絶対値に関する情報の欠損が発生し、データの誤解釈につながる可能性がある。
|
ルール |
深刻度 |
可能性 |
自動検出 |
自動修正 |
優先度 |
レベル |
|---|---|---|---|---|---|---|
|
NUM12-J |
低 |
低 |
可 |
可 |
P3 |
L3 |
自動検出
整数型の縮小変換の自動検出は容易である。それらの変換がプログラマが意図したものかどうかを判断するのは一般に不可能である。ヒューリスティックな警告メッセージは役に立つかもしれない。
| ツール | バージョン | チェッカー | 説明 |
|---|---|---|---|
| CodeSonar | 9.0p0 |
JAVA.MATH.APPROX.E |
Approximate e Constant |
| Parasoft Jtest | 2024.2 | CERT.NUM12.CLP | Do not cast primitive data types to lower precision |
| PVS-Studio |
7.38 |
V6124 |
|
関連ガイドライン
|
INT31-C. 整数変換によってデータの消失や解釈間違いが発生しないことを保証する |
|
| ISO/IEC TR 24772:2010 |
Numeric Conversion Errors [FLC] |
|
CWE-681, Incorrect Conversion between Numeric Types |
参考文献
翻訳元
これは以下のページを翻訳したものです。
NUM12-J. Ensure conversions of numeric types to narrower types do not result in lost or misinterpreted data (revision 113)
