Java言語では、プリミティブ型doubleよりも多くの指数ビットを持つ拡張浮動小数点数を提供するハードウェアの使用をプラットフォームに許している。したがって、そのようなプラットフォームでは、標準浮動小数点数型が表現できるよりも広い範囲の値を表現することができ、浮動小数点数演算の結果が標準のfloatやdoubleだけを使って行われる演算の結果と異なることがある。Java言語仕様§15.4「FP-strict式」には以下のように規定されている。
FP-strictでない式中では、極端な場合、float数値集合だけあるいはdouble数値集合だけを使用する計算ではオーバーフローやアンダーフローが発生するような場合でも「正しい答」が得られることがある。
異なるJVMやプラットフォームにおいて同じ浮動小数点数演算結果が得られることが要求されるプログラムでは、strictfp修飾子を用いなければならない。この修飾子を用いると、JVMやプラットフォームは標準のfloatやdoubleが表現できる値の範囲だけを使用して浮動小数点数演算を行うかのように動作する。その結果、異なるJVMやプラットフォーム間で同じ計算結果が得られることが保証される。
strictfp修飾子を用いたとしても、プラットフォーム固有の拡張浮動小数点数をサポートしないプラットフォームにおける実行は何ら変わらない。しかし、プラットフォーム固有の浮動小数点数をサポートするプラットフォームで行われる浮動小数点数計算は、演算速度と結果の値の両面で大きな影響を受ける。このようなプラットフォームで strictfp修飾子を用いると、計算途中にオーバーフローやアンダーフローが発生する可能性が高くなる。なぜなら、計算途中の値を表現できる範囲が制限されるからである。計算速度も低下するかもしれない。プログラムの可搬性(portability)が問題となる場合、これらの問題は避けて通ることができない。
strictfp修飾子はクラス、メソッド、インタフェースに適用することができる。
対象 | 適用範囲 |
---|---|
クラス | クラス内のすべてのコード (インスタンス、変数、静的初期化子(static initializer))と入れ子クラスのコード |
メソッド | メソッド内のすべてのコード |
インタフェース | インタフェースを実装するクラスのすべてのコード |
式がFP-strictであるとは、その式を含むクラス、メソッド、インタフェースのいずれかがstrictfp宣言されている場合を指す。浮動小数点数演算を含む定数式もFP-strictに従って評価される。コンパイル時の定数式は常に FP-strict である。
FP-strict であるスーパークラスを拡張するサブクラスには FP-strict の動作は継承されない。オーバーライドされるメソッドが FP-strict でない場合、それをオーバーライドするメソッドは個別に FP-strict 化するかどうかを選択することができる。またその逆も可能である。
違反コード
以下の違反コードでは、FP-strict に従った計算が行われることを求めていない。式の評価順序に従い、Double.MAX_VALUEに1.1をかけ、次に1.1で割っている。Double.MAX_VALUEがプラットフォームで表現できる最大値である場合、計算結果はinfinityになるだろう。
しかし、拡張浮動小数点数をサポートするプラットフォームでは、Double.MAX_VALUEとほとんど同じ値を出力するかもしれない。
JVM実装は、このケースを FP-strict として取り扱うこともでき、その場合にはオーバーフローが発生する。式は FP-strict であるため、JVM実装は計算の途中結果を拡張指数部を使って表現するかもしれない。
class Example { public static void main(String[] args) { double d = Double.MAX_VALUE; System.out.println("This value \"" + ((d * 1.1) / 1.1) + "\" cannot be represented as double."); } }
適合コード
可搬性を最大限確保するには、式中(クラス、メソッド、インタフェース)にstrictfpを使用し、計算の途中結果が実装依存の動作の影響を受けないことを保証すること。以下の適合コードでは、計算結果は、プラットフォームがどのような拡張浮動小数点数演算をサポートするかに関係なく、計算途中のオーバーフロー条件に従い、必ずinfinityとなる。
strictfp class Example { public static void main(String[] args) { double d = Double.MAX_VALUE; System.out.println("This value \"" + ((d * 1.1d) / 1.1d) + "\" cannot be represented as double."); } }
また、上記のコードでは、浮動小数点数リテラル1.1をdouble型に指定することで、期待される型を明確にするとともに、精度を最大化している。
違反コード
プラットフォーム固有の浮動小数点数ハードウェアはdoubleよりも広い範囲の値を表現することができる。そのようなプラットフォームでは、実行時コンパイラ(JIT)は、strictfp 修飾子がなくても float型もしくはdouble型の値を保持するために浮動小数点数レジスタを使ってもよいが、浮動小数点数レジスタはこれらのプリミティブ型の指数部よりも大きな指数を持つ値をサポートしているかもしれない。したがって、floatからdoubleへの変換により絶対値を表現するビットの一部が失われてしまうかもしれない。
class Example { double d = 0.0; public void example() { float f = Float.MAX_VALUE; float g = Float.MAX_VALUE; this.d = f * g; System.out.println("d (" + this.d + ") might not be equal to " + (f * g)); } public static void main(String[] args) { Example ex = new Example(); ex.example(); } }
float型のフィールドへ値を格納するように、メモリに保存するだけでも、絶対値を表現するビットの一部が失われることがある。
適合コード
以下の適合コードでは、Java標準の浮動小数点数に厳密に適合するためにstrictfpキーワードを用いている。したがって、2つのf * gの結果の値は、 拡張指数部をサポートするプラットフォームにおいても、this.dに格納されている値と一致する。
strictfp class Example { double d = 0.0; public void example() { float f = Float.MAX_VALUE; float g = Float.MAX_VALUE; this.d = f * g; System.out.println("d (" + this.d + ") might not be equal to " + (f * g)); } public static void main(String[] args) { Example ex = new Example(); ex.example(); } }
例外
NUM06-EX0: このルールは、すべてのプラットフォームで浮動小数点数演算の結果が同じであることが求められる場合のみに適用される。その必要がないプログラムは本ルールに適合する必要はない。
NUM06-EX1:適切な数値解析により、計算結果がアプリケーションが求める精度と動作を満たすことを証明できる場合であれば、strictfp修飾子を省略してもよい。
リスク評価
strictfp修飾子を使用しないと、浮動小数点数演算に関する動作が可搬性のない実装に左右されるおそれがある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
NUM06-J | 低 | 低 | 高 | P1 | L3 |
自動検出
一般に本ルールの違反を自動検出するのは不可能である。
関連ガイドライン
The CERT C Secure Coding Standard | FLP00-C. Understand the limitations of floating point numbers |
CERT C++ Secure Coding Standard | FLP00-CPP. Understand the limitations of floating point numbers |
参考文献
[Darwin 2004] | Ensuring the Accuracy of Floating-Point Numbers |
[JLS 2005] | §15.4, "FP-strict Expressions" |
[JPL 2006] | 9.1.3. Strict and Non-Strict Floating-Point Arithmetic |
[McCluskey 2001] | JDC Tech Tips 2001年4月10日: strictfpの使用 |
翻訳元
これは以下のページを翻訳したものです。
NUM06-J. Use the strictfp modifier for floating-point calculation consistency across platforms (revision 74)