JavaRush /Java Blog /Random-JA /実数の装置

実数の装置

Random-JA グループに公開済み
こんにちは!今日の講義では、Java の数値、特に実数について話します。 実数の装置 - 1慌てないで!:) 講義には数学的な難しい問題はありません。ここでは、もっぱら「プログラマ」の観点から実数について説明します。では、「実数」とは何でしょうか? 実数は、小数部 (ゼロの場合もあります) を持つ数値です。それらはポジティブなものでもネガティブなものでも構いません。以下にいくつかの例を示します: 15 56.22 0.0 1242342343445246 -232336.11 実数はどのように機能するのでしょうか? 非常に単純です。整数部分、小数部分、および符号で構成されます。正の数の場合、符号は通常明示的に示されませんが、負の数の場合は明示的に示されます。以前、Java で数値に対してどのような操作を実行できるかを詳しく調べました。その中には、加算、減算などの標準的な数学演算が多数含まれていました。また、除算の余りなど、新しい演算もいくつかありました。しかし、コンピューター内で数値を扱うことは正確にどのように機能するのでしょうか? それらはどのような形式でメモリに保存されますか?

実数をメモリに保存する

数字が大きいことも小さいこともあるということは、あなたにとって発見ではないと思います :) それらは互いに比較することができます。たとえば、数値 100 は数値 423324 より小さいです。これはコンピューターとプログラムの動作に影響しますか? 実はそうです。Java では、各数値は特定の範囲の値で表されます。
タイプ メモリサイズ(ビット) 値の範囲
byte 8ビット -128~127
short 16ビット -32768 ~ 32767
char 16ビット UTF-16 文字 (文字と数字) を表す符号なし整数
int 32ビット -2147483648 から 2147483647
long 64ビット -9223372036854775808 から 9223372036854775807
float 32ビット 2 -149から(2-2 -23 )*2 127
double 64ビット 2 -1074から(2-2 -52 )*2 1023
float今日は最後の 2 つのタイプ、およびについて説明しますdouble。どちらも小数を表す同じタスクを実行します。これらは「浮動小数点数」ともよく呼ばれます。将来のためにこの用語を覚えておいてください :) たとえば、数値 2.3333 または 134.1212121212 です。かなり奇妙。結局のところ、これら 2 つのタイプは同じタスクを実行するため、違いはないことがわかりますか? しかし、違いがあります。上の表の「メモリ内のサイズ」列に注目してください。すべての数値 (数値だけでなく、情報全般) はビットの形式でコンピューターのメモリに保存されます。ビットとは情報の最小単位です。とてもシンプルです。どのビットも 0 または 1 に等しくなります。また、「ビット」という言葉自体は、英語の「バイナリ ディジット」、つまり 2 進数に由来しています。数学における 2 進数システムの存在についてはおそらく聞いたことがあると思います。私たちがよく知っているあらゆる 10 進数は、1 と 0 のセットとして表すことができます。たとえば、数値 584.32 を 2 進数で表すと、100100100001010001111のようになります。この数値の 1 と 0 はそれぞれ別のビットです。これで、データ型の違いがより明確になるはずです。たとえば、 type の数を作成した場合float、自由に使えるのは 32 ビットだけです。数値を作成するとき、floatこれはコンピューターのメモリ内で数値に割り当てられる正確なスペースの量です。数値 123456789.65656565656565 をバイナリで作成したい場合は、 11101011011110011010001010110101000000のようになります 。これは 38 個の 1 と 0 で構成されます。つまり、メモリに保存するには 38 ビットが必要です。この数値は単にタイプにfloat「適合」しません。したがって、数値 123456789 は type として表すことができますdouble。それを保存するために 64 ビットものビットが割り当てられます。これは私たちにぴったりです。もちろん、値の範囲も適切になります。便宜上、数値をセルが入った小さなボックスと考えることができます。各ビットを格納するのに十分なセルがある場合、データ型は正しく選択されています :) 実数の装置 - 2もちろん、割り当てられたメモリの量の違いも数値自体に影響します。タイプによって値の範囲が異なることfloatに注意してください。doubleこれは実際には何を意味するのでしょうか? 数値はdouble数値よりも高い精度を表現できますfloat。32 ビット浮動小数点数 (Java では、これはまさに type float) の精度は約 24 ビット、つまり小数点以下約 7 桁です。また、64 ビット数値 (Java では type double) の精度は約 53 ビット、つまり小数点以下約 16 桁です。この違いをよく示す例を次に示します。

public class Main {

   public static void main(String[] args)  {

       float f = 0.0f;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}
その結果、ここで何が得られるでしょうか? すべてが非常に単純であるように思われるでしょう。数値 0.0 があり、それに 0.1111111111111111 を 7 回連続して加えます。結果は 0.7777777777777777 となるはずです。しかし、私たちは数字を作りましたfloat。そのサイズは 32 ビットに制限されており、前述したように、小数点第 7 位までの数値を表示できます。したがって、最終的にコンソールに表示される結果は、予想したものとは異なります。

0.7777778
番号は「切り取られた」ようでした。データがビットの形式でメモリにどのように保存されるかはすでにご存知なので、これには驚かないでしょう。なぜこれが起こったのかは明らかです。結果 0.7777777777777777 は、単に割り当てられた 32 ビットに収まらなかったため、型変数に収まるように切り捨てられました:)この例では、変数の型を次のようにfloat変更できます。double結果は切り捨てられません:

public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}

0.7777777777777779
すでに小数点以下 16 桁があり、結果は 64 ビットに「収まります」。ところで、どちらの場合も結果が完全に正しいわけではないことに気づいたでしょうか? 計算には若干の誤差がありました。その理由については以下で説明します :) 次に、数値を相互に比較する方法について少しお話しましょう。

実数の比較

前回の講義で比較演算について説明したときに、この問題について部分的にすでに触れました。><、などの操作を再解析することはありません>=<=代わりに、より興味深い例を見てみましょう。

public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 10; i++) {
           f += 0.1;
       }

       System.out.println(f);
   }
}
画面に表示される数字は何だと思いますか? 論理的な答えは、数値 1 です。数値 0.0 から数え始め、それに 0.1 を 10 回続けて加えます。すべてが正しいように見えますが、それは 1 つであるはずです。このコードを実行してみると、その答えはあなたを大いに驚かせるでしょう :) コンソール出力:

0.9999999999999999
しかし、なぜこのような単純な例でエラーが発生したのでしょうか? O_o ここでは小学 5 年生でも簡単に正解できましたが、Java プログラムは不正確な結果を出しました。ここでは「不正確」よりも「不正確」の方が適切な言葉です。単なるランダムな値ではなく、それでも 1 に非常に近い数値が得られました :) それは文字通り正しい値とは 1 ミリメートル異なります。しかし、なぜ?おそらくこれは一度限りの間違いです。もしかしてコンピューターが壊れたのでしょうか?別の例を書いてみましょう。

public class Main {

   public static void main(String[] args)  {

       //add 0.1 to zero eleven times in a row
       double f1 = 0.0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multiply 0.1 by 11
       double f2 = 0.1 * 11;

       //should be the same - 1.1 in both cases
       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       // Let's check!
       if (f1 == f2)
           System.out.println("f1 and f2 are equal!");
       else
           System.out.println("f1 and f2 are not equal!");
   }
}
コンソール出力:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
したがって、これは明らかにコンピューターの不具合の問題ではありません:) 何が起こっているのでしょうか? このようなエラーは、コンピューターのメモリ内で数値がバイナリ形式で表現される方法に関連しています。実際のところ、二進法では 0.1 という数値を正確に表現することは不可能です。ちなみに、10 進法にも同様の問題があります。分数を正しく表すことは不可能です (1/3 の代わりに 0.33333333333333... が得られますが、これも完全に正しい結果ではありません)。それは些細なことのように思えるかもしれません。このような計算では、その差は 10 万分の 1 (0.00001) またはそれ以下になる可能性があります。しかし、あなたの非常に真剣なプログラムの結果全体がこの比較に依存しているとしたらどうなるでしょうか?

if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
私たちは 2 つの数値が等しいことを明らかに予想していましたが、内部メモリの設計により、ロケットの打ち上げは中止されました。 実数の装置 - 3その場合、比較結果がより予測可能になるように、2 つの浮動小数点数を比較する方法を決定する必要があります。したがって、実数を比較するときのルールその 1 は、実数を比較するときに浮動小数点数を決して使用しないことをすでに学習しました。== OK、悪い例はこれで十分だと思います :) 良い例を見てみましょう!

public class Main {

   public static void main(String[] args)  {

       final double threshold = 0.0001;

       //add 0.1 to zero eleven times in a row
       double f1 = .0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multiply 0.1 by 11
       double f2 = .1 * 11;

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       if (Math.abs(f1 - f2) < threshold)
           System.out.println("f1 and f2 are equal");
       else
           System.out.println("f1 and f2 are not equal");
   }
}
ここでは基本的に同じことを行っていますが、数値を比較する方法が異なります。特別な「しきい値」番号、0.0001、つまり 10,000 分の 1 があります。違うかもしれません。それは、特定のケースでどの程度正確な比較が必要かによって異なります。大きくしたり小さくしたりできます。このメソッドを使用して、Math.abs()数値の係数を取得します。係数は、符号に関係なく数値の値です。たとえば、数値 -5 と 5 は同じ係数を持ち、5 に等しくなります。最初の数値から 2 番目の数値を減算し、その結果の結果が、符号に関係なく、設定したしきい値より小さい場合は、次のようになります。私たちの数は等しいです。 いずれの場合も、それらは「しきい値数値」を使用して確立した精度の程度と等しくなります。つまり、少なくとも 10,000 分の 1 までは等しくなります。この比較方法により、 の場合に見られた予期せぬ動作を回避できます==。実数を比較するもう 1 つの良い方法は、特別なクラスを使用することですBigDecimal。このクラスは、小数部を含む非常に大きな数値を格納するために特別に作成されました。doubleや とは異なりfloat、加算、減算、その他の数学演算は演算子 (など)BigDecimalを使用せずにメソッドを使用して実行されます。+-私たちの場合は次のようになります。

import java.math.BigDecimal;

public class Main {

   public static void main(String[] args)  {

       /*Create two BigDecimal objects - zero and 0.1.
       We do the same thing as before - add 0.1 to zero 11 times in a row
       In the BigDecimal class, addition is done using the add () method */
       BigDecimal f1 = new BigDecimal(0.0);
       BigDecimal pointOne = new BigDecimal(0.1);
       for (int i = 1; i <= 11; i++) {
           f1 = f1.add(pointOne);
       }

       /*Nothing has changed here either: create two BigDecimal objects
       and multiply 0.1 by 11
       In the BigDecimal class, multiplication is done using the multiply() method*/
       BigDecimal f2 = new BigDecimal(0.1);
       BigDecimal eleven = new BigDecimal(11);
       f2 = f2.multiply(eleven);

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       /*Another feature of BigDecimal is that number objects need to be compared with each other
       using the special compareTo() method*/
       if (f1.compareTo(f2) == 0)
           System.out.println("f1 and f2 are equal");
       else
           System.out.println("f1 and f2 are not equal");
   }
}
どのようなコンソール出力が得られるでしょうか?

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
まさに期待通りの結果が得られました。そして、数値がどれほど正確であるか、そして小数点以下の桁数が何桁に収まるかに注目してください。よりもはるかに多く、floatさらにはdouble!将来のためにこのクラスを覚えておいてくださいBigDecimal。必ず必要になります:) ふう!講義はかなり長かったですが、よくやり遂げました。:) 将来のプログラマー、次のレッスンでお会いしましょう!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION