Java™语言规范的版本5向java.lang.Math
和java.lang.StrictMath
添加了10个新方法,而Java 6添加了另外10 个方法 。本两篇系列文章的第1部分介绍了在数学上有意义的新方法。 也就是说,它们提供了计算机时代以前的数学家会熟悉的功能。 在第2部分中,我着重介绍仅当您意识到它们被设计用于浮点数而不是抽象实数时才有意义的功能。
正如我在第1部分中提到的那样,实数(例如e或0.2)与其计算机表示形式(例如Java double
)之间的区别是重要的。 该数字的柏拉图式理想是无限精确的,而Java表示仅具有固定数量的位数( float
为32, double
float
数为64)。 float
的最大值约为3.4 * 10 38 ,对于某些您可能希望表示的事物(例如宇宙中的电子数)而言,还不够大。
double
精度数最多可以表示1.8 * 10 308的数字 ,几乎涵盖了我能想到的任何物理量。 但是,在对抽象数学量进行计算时,可能会超过这些值。 例如,仅171个! (171 * 170 * 169 * 168 * ... * 1)足以超出double
的范围。 float
仅35点就超出范围! 小数字(即接近零的数字)也可能会产生问题,并且涉及大数字和小数字的计算都可能具有正危险性。
要解决此问题,用于浮点数学运算的IEEE 754标准(请参阅参考资料 )添加了特殊值Inf表示Infinity和NaN表示“ Not a Number”。 IEEE 754还定义了正零和负零。 (在常规数学中,零既不是正数也不是负数。在计算机数学中,它既可以是负数也可以是负数。)这些值在经典证明中造成严重破坏。 例如,当使用NaN时,排除的中间定律不再成立。 x == y或x!= y不一定是正确的。 如果x(或y)为NaN,则两者都可能为假。
除了数量级问题之外,精度甚至是更实际的问题。 我们都已经看到了这样的循环,您将其加0.1一百次,最后得到9.99999999999998而不是10:
for (double x = 0.0; x <= 10.0; x += 0.1) {
System.err.println(x);
}
对于简单的应用程序,通常只要求java.text.DecimalFormat
将最终输出格式化为最接近的整数,然后将其命名为day。 但是,在不确定工程是否可以使用整数的科学和工程应用中,您需要格外小心。 如果要互相减去大数得到一个小数,则需要非常小心。 如果要除以那个小数目,则仍然需要更加小心。 当将答案应用到物理世界中时,此类操作甚至可以将微小的错误急剧放大为大错误,从而产生明显的后果。 有限精度浮点数引起的小的舍入误差会严重歪曲数学上精确的计算结果。
浮点数和双精度数的二进制表示
用Java语言实现的IEEE 754浮点数有32位。 第一位是符号位,0表示正,1表示负。 接下来的八位是指数,可以保存-125至+127的值。 最后的23位保留尾数(有时称为有效尾数),范围从0到33554554。 将它们放在一起,浮点数将解释为sign * mantissa * 2 exponent
。
细心的读者可能会注意到,这些数字并没有完全相加。 首先,指数的八位应代表-128至127,就像带符号的字节一样。 但是,指数有126的偏差。即,您从无符号值(0到255)开始,然后减去126得到真实的指数,现在是-126到128。好吧,除了128和-126是特殊的价值观。 当指数全为1位(128)时,表示该数字为Inf,-Inf或NaN。 要弄清楚哪个,您必须查看尾数。 当指数全为零位(-126)时,即表示该信号已被非规格化 (更多有关这的含义),但指数仍为-125。
尾数基本上是一个23位无符号整数-很简单。 23位可以容纳0到2 24 -1之间的数字,即16,777,215。 等一下,我不是说尾数从0到33,554,431不等吗? 那是2 25 -1。 多余的钱从哪里来?
事实证明,您可以使用指数来判断第一位是什么。 如果指数全为零,则第一位为零。 否则,第一位为1。 因为您始终知道第一位是什么,所以不必将其包括在数字中。 您可以免费获得一点额外费用。 偷偷摸摸的吧?
尾数的第一位为1的浮点数已标准化 。 即,尾数始终具有介于1和2之间的值。即使指数始终为-125,尾数的第一位为零的浮点数也被归一化,并且可以表示更小的数字。
除了使用52位尾数和11位指数以提高精度外,双精度码的编码方式几乎相同。 双精度指数的偏差为1023。