JPCERT コーディネーションセンター

NUM13-J. プリミティブ整数を浮動小数点数に変換する際、精度を低下させない

以下に挙げる19の型変換は、拡張プリミティブ型変換(widening primitive conversion)と呼ばれる。

intあるいはlongからfloatへ、もしくはlongからdoubleへの型変換は、精度の低下(最下位ビットが失われる)を伴う。これらの変換では、変換後の浮動小数点数は、IEEE 754による最近接丸め(round-to-nearest)モードに基づき、整数を丸めた値となる。精度の低下が発生するにも関わらず、Java言語仕様では、実行時例外をスローすることなく変換や丸めこみを行うことを要求している。詳しくはJava言語仕様§5.1.2「拡張プリミティブ変換」を参照。intより小さな整数型から浮動小数点型への変換や、intからdoubleへの変換では精度の低下は発生しない。したがって、intもしくはlongから浮動小数点数型への変換あるいは、longからdoubleへの変換において、必要とされる精度が低下しないように注意しなくてはならない。

floatからdoubleへの変換においても絶対値を表すビットの一部が失われる可能性があることに注意。詳細は「NUM06-J. どのプラットフォームでも一貫した浮動小数点数演算を行うために strictfp 修飾子を使う」を参照。

違反コード

以下の違反コードでは、2つの大きな、同じ値の整数リテラルをsubFloatFromInt()メソッドの引数に渡している。第2引数はfloatに変換され、次にintにキャストされ、最後にint型の値から減算されている。結果はint型の値として呼び出し元に返される。

精度の低下が原因で、このメソッドは予期せぬ計算結果を返すかもしれない。FP-strict モードでは、float型の値は仮数部23ビット、符号1ビット、指数部8ビットを持つ。FP-strict モードの詳細については「NUM06-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)

以下の解決法では、整数引数(op1)の範囲検査を行い、精度の低下を引き起こすことなくfloat型の値として表現できることを保証している。

strictfp class WideSample {
  public static int subFloatFromInt(int op1, float op2) 
                    throws ArithmeticException {
 
    // 仮数部は最大23ビット保持できる
    if ((op2 > 0x007fffff) || (op2 < -0x800000)) {
      throw new ArithmeticException("Insufficient precision"); 
    }
 
    return op1 - (int)op2;
  }
 
  public static void main(String[] args) {
    int result = subFloatFromInt(1234567890, 1234567890);
    System.out.println(result); 
  }
}

この例では、subFloatFromInt()メソッドは例外ArithmeticExceptionをスローする。longからfloatdoubleへの変換を行う場合、このような値の範囲検査を行う方法をとることもできる。

適合コード (より大きな型を使う)

以下の適合コードでは、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-EX0: 最下位ビットの欠損が許容できることが数値解析上わかる場合、範囲検査を行わずに整数型から浮動小数点数型へ変換してもよい。

リスク評価

整数値をそれより仮数部のビット数の少ない浮動小数点数に変換すると、丸めエラーを引き起こすことがある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
NUM13-J P2 L3
自動検出

精度の低下を招くキャストを自動検出するのは容易である。そのようなキャストが、プログラマが意図したものかどうかを判断するのは一般に不可能である。ヒューリスティックな警告は役に立つであろう。

関連ガイドライン
The CERT C Secure Coding Standard FLP36-C. Beware of precision loss when converting integral types to floating point
The CERT C++ Secure Coding Standard FLP36-CPP. Beware of precision loss when converting integral types to floating point
参考文献
[JLS 2005] §5.1.2, "Widening Primitive Conversion"
翻訳元

これは以下のページを翻訳したものです。

NUM13-J. Avoid loss of precision when converting primitive integers to floating-point (revision 116)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter