float リテラルを次のように宣言する必要がある理由について興味があります。
float f = 0.1f;
それ以外の
float f = 0.1;
デフォルトの型が double であるのはなぜですか? 割り当ての左側を見てコンパイラが float であると推測できないのはなぜですか? Google は、デフォルト値が何であるかについての説明を表示するだけで、その理由については表示しません。
float リテラルを次のように宣言する必要がある理由について興味があります。
float f = 0.1f;
それ以外の
float f = 0.1;
デフォルトの型が double であるのはなぜですか? 割り当ての左側を見てコンパイラが float であると推測できないのはなぜですか? Google は、デフォルト値が何であるかについての説明を表示するだけで、その理由については表示しません。
デフォルトの型が double なのはなぜですか?
これは、Java 言語の設計者に最もよく尋ねられる質問です。言語設計の決定が下された本当の理由を知っているのは彼らだけです。しかし、その理由は次のようなものだったと思います。
2 種類のリテラルを区別する必要があったのは、実際には異なる値を意味するためです...数学的な観点からです。
彼らがリテラルのデフォルトを「float」にしたと仮定して、この例を考えてみましょう
// (Hypothetical "java" code ... )
double d = 0.1;
double d2 = 0.1d;
上記のd
andd2
は、実際には異なる値になります。最初のケースでは、割り当ての時点で精度の低い値が精度float
の高い値に変換されます。double
しかし、そこにない精度を回復することはできません。
これら2つのステートメントが両方とも合法であり、異なることを意味する言語設計は悪い考えだと私は仮定します...最初のステートメントの実際の意味が「自然な」意味とは異なることを考えると.
彼らが行った方法でそれを行うことによって:
double d = 0.1f;
double d2 = 0.1;
どちらも合法であり、また異なることを意味します。しかし、最初のステートメントでは、プログラマーの意図は明確であり、2 番目のステートメントでは、プログラマーが「自然な」意味を理解します。そしてこの場合:
float f = 0.1f;
float f2 = 0.1; // compilation error!
...コンパイラは不一致を検出します。
float の使用は例外であり、最新のハードウェアでは (代わりに double を使用する) ルールではないと推測しているため、ある時点で、ユーザーが書き込み時に 0.1f を意図していると想定することは理にかなっています。
float f = 0.1;
彼らはすでにそれを行うことができました。しかし問題は、機能する型変換規則のセットを考え出すことです...そして実際に理解するのにJava学の学位を必要としないほど単純です。0.1
異なる文脈で異なる意味を持つことは、混乱を招くでしょう。そして、これを考慮してください:
void method(float f) { ... }
void method(double d) { ... }
// Which overload is called in the following?
this.method(1.0);
プログラミング言語の設計はトリッキーです。ある領域の変化は、他の領域に影響を与える可能性があります。
@supercatによって提起されたいくつかのポイントに対処するために更新します。
@supercat: 上記のオーバーロードを考えると、メソッド (16777217) に対してどのメソッドが呼び出されますか? それが最良の選択ですか?
間違ってコメントしました...コンパイルエラー。実際、答えはmethod(float)
です。
JLS は次のように述べています。
複数のメンバー メソッドがアクセス可能で、メソッド呼び出しに適用できる場合は、ランタイム メソッド ディスパッチの記述子を提供するために 1 つを選択する必要があります。Java プログラミング言語では、最も具体的な方法が選択されるという規則が使用されます。
...
[記号 m1 と m2 は、適用可能なメソッドを示します。]
[If] m2 がジェネリックではなく、m1 と m2 がstrictまたは Loose 呼び出しによって適用可能であり、m1 が仮パラメーター型 S1、...、Sn を持ち、m2 が仮パラメーター型 T1、...、Tn を持つ場合、型Si は、すべての i (1 ≤ i ≤ n、n = k) の引数 ei の Ti よりも具体的です。
...
上記の条件は、ある方法が別の方法よりも具体的である可能性がある唯一の状況です。
S <: T (§4.10) の場合、任意の式について、型 S は型 T よりも具体的です。
この場合、 と を比較method(float)
しmethod(double)
ており、どちらも呼び出しに適用されます。float
<:double
であるため、より具体的であるため、method(float)
選択されます。
@supercat: このような動作は、たとえばorのような式が
int2 = (int) Math.Round(int1 * 3.5)
orlong2 = Math.Round(long1 * 3.5)
に置き換えられた場合に問題を引き起こす可能性がありますint1 = (int) Math.Round(int2 * 3)
long2 = Math.Round(long1 * 3)
613566756
変更は無害に見えますが、最初の 2 つの式はorまで正しく2573485501354568
、後の 2 つは上記では失敗します5592405
[最後の 2 つ以上は完全に偽物です715827882
]。
あなたがその変化を起こしている人について話しているのなら...そうですね。
ただし、コンパイラは背後でその変更を行いません。たとえば、 にint1 * 3.5
は型があるdouble
(int
は に変換されるdouble
) ため、 を呼び出すことになりMath.Round(double)
ます。
原則として、Java 算術演算は暗黙的に「小さい」数値型から「大きい」数値型に変換しますが、「大きい」数値型から「小さい」数値型には変換しません。
ただし、(丸めの例では)次のように注意する必要があります。
float
整数と浮動小数点の積は、(たとえば)は よりも精度のビットが少ないため、十分な精度で表現できない場合がありint
ます。
Math.round(double)
の結果を整数型にキャストすると、整数型の最小値/最大値に変換される可能性があります。
しかし、これらすべては、プログラミング言語での算術サポートがトリッキーであることを示しており、初心者または不注意なプログラマーには避けられない落とし穴があります。
ハ、これは氷山の一角にすぎません。
F
他の言語から来たプログラマーは、次のものと比較して、リテラルに少し追加する必要があることを確かに気にしません。
SomeReallyLongClassName x = new SomeReallyLongClassName();
かなり冗長ですよね?
より多くのバックグラウンドを得るために、コア Java 開発者自身と話をしなければならないことは事実です。しかし、純粋な表面レベルの説明として、理解すべき重要な概念の 1 つは、式とは何かということです。Java では (私は専門家ではないので、これは一粒の塩で考えてください)、コンパイラ レベルでは、コードは式の観点から分析されると思います。それで:
float f
タイプを持ち、
0.1f
タイプ ( float
) もあります。
一般的に言えば、ある式を別の式に代入する場合、型が一致している必要があります。この規則が緩和される非常に特殊なケースがいくつかあります (たとえば、 のようなint
参照型でプリミティブをボックス化するなどInteger
)。しかし、一般的には成り立ちます。
この場合はばかげているように見えるかもしれませんが、それほどばかげていないように見える非常によく似たケースを次に示します。
double getDouble() {
// some logic to return a double
}
void example() {
float f = getDouble();
}
この場合、コンパイラが何か問題を検出するのは理にかなっていることがわかります。によって返される値getDouble
の精度は 64 ビットですがf
、32 ビットしか含めることができません。したがって、明示的なキャストがなければ、プログラマーがミスを犯した可能性があります。
これら 2 つのシナリオは、人間の観点からは明らかに異なります。しかし、式について私が言いたいのは、コードを最初に式に分解してから分析すると、それらは同じであるということです。
コンパイラの作成者は、リテラルが割り当てられている式のタイプに基づいてリテラルを再解釈するための、それほど巧妙ではないロジックを作成できたはずです。彼らは単にしませんでした。おそらく、他の機能と比較して、努力する価値はないと考えられていました.
概観すると、多くの言語が型推論を行うことができます。たとえば、C# では次のようにできます。
var x = new SomeReallyLongClassName();
の型はx
、その割り当てに基づいてコンパイラによって推測されます。
ただし、リテラルについては、C# はこの点で Java と同じです。
float
精度が非常に低いため、より興味深い質問は次のとおりです。なぜそれがまったくサポートされているのですか?float
一部のメモリを同じにすることができる(数百万のメモリがある場合)、またはフロートを期待するものとデータを交換するためにそれらが必要になるまれな状況があります。
一般に、 を使用double
する方が適切な選択であり、最新の PC とほぼ同じ速度であり、精度が向上することに比べてメモリの節約はわずかです。
Java は、値がどのように使用されているかを確認するために、どのような状況でも左側を調べません。たとえば、メソッドの戻り値の型は署名の一部ではありません。場合によっては、代入および演算子の代入で暗黙的にキャストダウンしますが、これは通常、C との互換性を維持するためであり、アドホックな IMHO です。