JavaRush /Java 博客 /Random-ZH /实数装置

实数装置

已在 Random-ZH 群组中发布
你好!在今天的讲座中,我们将讨论 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
今天我们将讨论最后两种类型 -floatdouble。两者执行相同的任务 - 表示小数。它们也经常被称为“浮点数”。请记住这个术语以备将来使用:) 例如,数字 2.3333 或 134.1212121212。很奇怪。毕竟,事实证明这两种类型之间没有区别,因为它们执行相同的任务?但有一个区别。请注意上表中的“内存大小”列。所有数字(不仅仅是数字 - 一般而言所有信息)都以位的形式存储在计算机内存中。位是最小的信息单位。这很简单。任何位都等于 0 或 1。“”一词本身来自英语“二进制数字”——二进制数。我想您可能听说过数学中二进制数字系统的存在。我们熟悉的任何十进制数都可以表示为一组 1 和 0。例如,二进制数 584.32 如下所示: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 中正是 类型float)的精度约为 24 位,即大约 7 位小数。64 位数字(在 Java 中是 类型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 位,并且正如我们之前所说,它能够显示最多小数点后第七位的数字。因此,最终我们在控制台得到的结果会和我们预想的不一样:

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。一切似乎都是正确的,应该是一回事。尝试运行这段代码,答案会让你大吃一惊:) 控制台输出:

0.9999999999999999
但是这么简单的例子为什么会出现错误呢?O_o 这里即使是五年级学生也能轻松回答正确,但 Java 程序产生的结果不准确。“不准确”在这里比“不正确”更好。我们仍然得到一个非常接近 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。顺便说一句,十进制也有类似的问题:无法正确表示分数(我们得到的不是 ⅓ 而是 0.33333333333333...,这也不是完全正确的结果)。这似乎是一件小事:通过这样的计算,差异可能是万分之一 (0.00001) 甚至更小。但是,如果您的 Very Serious 计划的整个结果取决于这种比较怎么办?
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
我们显然期望这两个数字相等,但由于内存设计,我们取消了火箭发射。 实数装置 - 3如果是这样,我们需要决定如何比较两个浮点数,以便比较结果更......嗯......可预测。因此,我们已经学会了比较实数时的第一条规则:比较实数时切勿使用==浮点数。 好吧,我认为坏例子已经够多了:)让我们看一个好例子!
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,即万分之一。可能会有所不同。这取决于您在特定情况下需要的比较的精确程度。您可以将其放大或缩小。使用该方法,Math.abs()我们可以获得数字的模。模数是数字的值,与符号无关。例如,数字 -5 和 5 将具有相同的模数并等于 5。我们从第一个数字中减去第二个数字,如果结果结果(无论符号如何)小于我们设置的阈值,则我们的人数是相等的。 无论如何,它们等于我们使用“阈值数”建立的准确度,也就是说,它们至少等于万分之一。这种比较方法将使您避免出现我们在 的情况下看到的意外行为==。比较实数的另一个好方法是使用特殊的类BigDecimal。此类是专门为存储带有小数部分的非常大的数字而创建的。double与and不同的是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比 in甚至 in多得多doubleBigDecimal记住将来的课程,你肯定会需要它:)唷!讲座很长,但你做到了:干得好!:) 下一课见,未来的程序员!
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION