NUM13-J. プリミティブ整数を浮動小数点数に変換する際、精度を低下させない
以下に挙げる19の型変換は、「プリミティブ型の拡大変換」と呼ばれる。
byteからshort,int,long,float, またはdoubleshortからint,long,float, またはdoublecharからint,long,float, またはdoubleintからlong,float, またはdoublelongからfloat, またはdoublefloatからdouble
intあるいはlongからfloatへ、もしくはlongからdoubleへの型変換は、精度の低下 (最下位ビットの欠損) を伴う。これらの変換では、変換後の浮動小数点数は、IEEE 754による最近接丸め (round-to-nearest) モードに基づき、整数を丸めた値となる。精度の低下が発生するにも関わらず、『Java言語仕様』では、実行時例外をスローすることなく変換や丸めこみを行うことを要求している (詳しくはJava言語仕様§5.1.2「プリミティブ型の拡大変換」[JLS 2015] を参照)。intより小さな整数型から浮動小数点数型への変換や、intからdoubleへの変換では精度の低下は発生しない。したがって、intもしくはlongから浮動小数点数型への変換あるいは、longからdoubleへの変換において、必要とされる精度が低下しないように注意しなくてはならない。
floatからdoubleへの変換においても絶対値を表すビットの一部が失われる可能性があることに注意 (詳細は「NUM53-J. どのプラットフォームでも一貫した浮動小数点数演算を行うために strictfp 修飾子を使う」を参照)。
違反コード
以下の違反コードでは、2つの大きな、同じ値の整数リテラルをsubFloatFromInt()メソッドの引数に渡している。第2引数はfloatに変換され、次にintにキャストされ、最後にint型の値から減算されている。結果はint型の値として呼び出し元に返される。
精度の低下が原因で、このメソッドは予期せぬ計算結果を返すかもしれない。FP-strictモードでは、float型の値は仮数部23ビット、符号部1ビット、指数部8ビットを持つ (FP-strictモードの詳細については「NUM53-J. どのプラットフォームでも一貫した浮動小数点数演算を行うために strictfp 修飾子を使う」を参照)。この指数部によってfloat型はint型よりも広い範囲の値を表現することができる。しかし、仮数部が23ビットであるということは、floatは23ビット以内で表現できる整数しか正確に表現できないということを意味する。言い換えると、その範囲外の整数の表現は近似値になるということである。
strictfp class WideSample {
public static int subFloatFromInt(int op1, float op2) {
return op1 - (int)op2;
}
public static void main(String[] args) {
int result = subFloatFromInt(1234567890, 1234567890);
// 想定される 0 ではなく、-46 を出力する
System.out.println(result);
}
}
longからfloatもしくはdoubleへの変換でも同様の精度低下が発生しうる。
適合コード (ArithmeticException)
以下の適合コードでは、op2がsubFloatFromInt()の第二引数として使われ、float型へ暗黙的にキャストされる前に範囲検査を行っている。この範囲検査は、第二引数が精度の低下を引き起こすことなくfloat型の値に変換できることを保証している。
strictfp class WideSample {
public static int subFloatFromInt(int op1, float op2) {
return op1 - (int)op2;
}
public static void main(String[] args) throws ArithmeticException {
int op1 = 1234567890;
int op2 = 1234567890;
// floatへの暗黙的キャストの前にop2を検査する。floatの仮数部は最大23ビット保持できる。
if ((op2 > 0x007fffff) || (op2 < -0x800000)) {
throw new ArithmeticException("Insufficient precision");
}
int result = subFloatFromInt(op1, op2);
System.out.println(result);
}
}
この例では、main()メソッドは例外ArithmeticExceptionをスローする。longからfloatやdoubleへの変換を行う場合にも、このような値の範囲検査を行う方法をとることができる。
適合コード (より大きな型を使う)
以下の適合コードでは、float型ではなくdouble型の引数を受け取る。FP-strictモードでは、double型の値は、仮数部52ビット、符号部1ビット、指数部11ビットをとる。int型およびそれより小さな型の整数値は、精度の低下を伴うことなくdoubleへ変換することができる。
strictfp class WideSample {
public static int subDoubleFromInt(int op1, double op2) {
return op1 - (int)op2;
}
public static void main(String[] args) {
int result = subDoubleFromInt(1234567890, 1234567890);
// 期待通りの動作をする
System.out.println(result);
}
}
プリミティブ整数の型がlongである場合、この解決法は使えないことに注意。Javaにはlong型のすべての範囲を表現できる仮数部を持つような浮動小数点数型は存在しない。
例外
NUM13-J-EX0: 最下位ビットの欠損を許容できることが数値解析上わかる場合、範囲検査を行わずに整数型から浮動小数点数型へ変換してもよい。
リスク評価
整数値をそれより仮数部のビット数の少ない浮動小数点数に変換すると、丸め誤差を引き起こすことがある。
|
ルール |
深刻度 |
可能性 |
自動検出 |
自動修正 |
優先度 |
レベル |
|---|---|---|---|---|---|---|
|
NUM13-J |
低 |
低 |
不可 |
不可 |
P1 |
L3 |
自動検出
精度の低下を招くキャストを自動検出するのは容易である。そのようなキャストが、プログラマが意図したものかどうかを判断するのは一般に不可能である。ヒューリスティックな警告は役に立つであろう。
| ツール | バージョン | チェッカー | 説明 |
|---|---|---|---|
| CodeSonar | 9.0p0 |
JAVA.CAST.FTRUNC |
Cast: integer to floating point |
| Parasoft Jtest | 2025.2 | CERT.NUM13.AIC | Avoid implicit casts from integer data types to floating point data types |
| PVS-Studio |
7.40 |
V6011 |
|
関連ガイドライン
参考文献
|
[JLS 2015] |
|
| [Seacord 2015] |
翻訳元
これは以下のページを翻訳したものです。
NUM13-J. Avoid loss of precision when converting primitive integers to floating-point (revision 164)
