你好!在今天的讲座中,我们将讨论 Java 中的数字,特别是实数。 不要恐慌!:) 讲座中不会有数学困难。我们将完全从“程序员”的角度来讨论实数。那么,什么是“实数”? 实数是具有小数部分(可以为零)的数字。它们可以是积极的,也可以是消极的。以下是一些示例: 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
和double
。两者执行相同的任务 - 表示小数。它们也经常被称为“浮点数”。请记住这个术语以备将来使用:) 例如,数字 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 位来存储它:这适合我们!当然,取值范围也会合适。为了方便起见,您可以将数字视为带有单元格的小盒子。如果有足够的单元来存储每位,则数据类型选择正确:) 当然,分配的内存量不同也会影响数字本身。请注意,类型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");
我们显然期望这两个数字相等,但由于内存设计,我们取消了火箭发射。 如果是这样,我们需要决定如何比较两个浮点数,以便比较结果更......嗯......可预测。因此,我们已经学会了比较实数时的第一条规则:比较实数时切勿使用==
浮点数。 好吧,我认为坏例子已经够多了:)让我们看一个好例子!
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多得多double
!BigDecimal
记住将来的课程,你肯定会需要它:)唷!讲座很长,但你做到了:干得好!:) 下一课见,未来的程序员!
GO TO FULL VERSION