NUM53-J. どのプラットフォームでも一貫した浮動小数点数演算を行うために strictfp 修飾子を使う
Java言語では、strictfp修飾子がない場合、プリミティブ型doubleよりも多くの指数ビットを持つ「拡張浮動小数点数」を提供するハードウェアの使用をプラットフォームに許している。したがって、そのようなプラットフォームでは、標準の浮動小数点数型が表現できるよりも広い範囲の値を表現することができ、浮動小数点数演算の結果が標準のfloatやdoubleだけを使って行われる演算の結果と異なることがある。Java言語仕様§15.4「FP-strict式」には以下のように規定されている [JLS 2005]。
大まかにいうと、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.1) / 1.1) + "\" cannot be represented as double.");
}
}
違反コード
プラットフォーム固有の浮動小数点数ハードウェアはdoubleよりも広い範囲の値を表現することができる。そのようなプラットフォームでは、strictfp修飾子がなければ、浮動小数点数レジスタがプリミティブ型よりも大きな指数部を持つ値をサポートしている場合でも、実行時コンパイラ (JIT) が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();
}
}
例外
NUM53-J-EX0: このルールは、すべてのプラットフォームで浮動小数点数演算の結果が同じであることが求められる場合のみに適用される。その必要がないプログラムは本ルールに適合する必要はない。
リスク評価
strictfp修飾子を使用しないと、浮動小数点数演算に関する動作の可搬性が損なわれ、実装に左右されるおそれがある。
|
ルール |
深刻度 |
可能性 |
自動検出 |
自動修正 |
優先度 |
レベル |
|---|---|---|---|---|---|---|
|
NUM53-J |
低 |
低 |
不可 |
不可 |
P1 |
L3 |
関連ガイドライン
|
VOID FLP00-CPP. Understand the limitations of floating-point numbers |
参考文献
|
Ensuring the Accuracy of Floating-Point Numbers |
|
|
[JLS 2005] |
|
|
[JPL 2006] |
9.1.3, Strict and Non-Strict Floating-Point Arithmetic |
|
Making Deep Copies of Objects, Using strictfp, and Optimizing String Performance |
翻訳元
これは以下のページを翻訳したものです。
NUM53-J. Use the strictfp modifier for floating-point calculation consistency across platforms (revision 106)
