首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

浮点数(4)

浮点数(4)

[size=1.0625]浮点数的有限精度会导致一个难以预料的结果:超过某个点时,x+1 == x 便是真的。例如,下面这个简单的循环实际上是无限的:
[size=1.0625]for (float x = 16777213f; x < 16777218f; x += 1.0f) {
System.out.println(x);
}

[size=1.0625]实际上,这个循环将在一个固定的点上停下来,准确的数字是 16,777,216。这个数字等于 224,在这个点上,ULP 比增量大。

[size=1.0625]Math.ulp()为测试提供一个实用的用途。很明显,我们一般不会比较两个浮点数是否完全相等。相反,我们检查它们是否在一定的容错范围内相等。例如,在 JUnit 中,像以下这样比较预期的实际浮点值:

[size=0.875]1


[size=0.875][size=0.875]assertEquals(expectedValue, actualValue, 0.02);




[size=1.0625]这表明实际值与预期值的偏差在 0.02 之内。但是,0.02 是合理的容错范围吗?如果预期值是 10.5 或 -107.82,则 0.02 是完全可以接受的。但当预期值为几十亿时,0.02 则与 0 没有什么区别。通常,就 ULP 进行测试时考虑的是相对错误。一般选择的容错范围在 1 至 10 ULP 之间,具体情况取决于计算所需的精度。例如,下面指定实际结果必须在真实值的 5 个 ULP 之内:

[size=0.875]1


[size=0.875][size=0.875]assertEquals(expectedValue, actualValue, 5*Math.ulp(expectedValue));




[size=1.0625]根据期望值不同,这个值可以是万亿分之一,也可以是数百万。
scalb[size=1.0625]Math.scalb(x, y)用 2y乘以 x,scalb是 “scale binary(二进法)” 的缩写。

[size=0.875]1


[size=0.875]2


[size=0.875][size=0.875]public static double scalb(float f, int scaleFactor)
[size=0.875] public static double scalb(double d, int scaleFactor)




[size=1.0625]例如,Math.scalb(3, 4)返回 3 * 24,即 3*16,结果是 48.0。也可以使用 Math.scalb()来实现 getMantissa():

[size=0.875]1


[size=0.875]2


[size=0.875]3


[size=0.875]4


[size=0.875][size=0.875]public static double getMantissa(double x) {
[size=0.875]    int exponent = Math.getExponent(x);
[size=0.875]    return x / Math.scalb(1.0, exponent);
[size=0.875] }




[size=1.0625]Math.scalb()和 x*Math.pow(2, scaleFactor)的区别是什么?实际上,最终的结果是一样的。任何输入返回的值都是完全一样的。不过,性能方面则存在差别。Math.pow()的性能是非常糟糕的。它必须能够真正处理一些非常少见的情况,比如对 3.14 采用幂 -0.078。对于小的整数幂,比如 2 和 3(或以 2 为基数,这比较特殊),通常会选择完全错误的算法。
[size=1.0625]我担心这会对总体性能产生影响。一些编译器和 VM 的智能程度比较高。一些优化器会将 x*Math.pow(2, y)识别为特殊情况并将其转换为 Math.scalb(x, y)或类似的东西。因此性能上的影响体现不出来。不过,我敢保证有些 VM 是没有这么智能的。例如,使用 Apple 的 Java 6 VM 进行测试时,Math.scalb()几乎总是比 x*Math.pow(2, y)快两个数量级。当然,这通常不会造成影响。但是在特殊情况下,比如执行数百万次求幂运算时,则需要考虑能否转换它们以使用 Math.scalb()。
Copysign[size=1.0625]Math.copySign()方法将第一个参数的标记设置为第二个参数的标记。最简单的实现如清单 4 所示:
清单 4. 可能实现的 copysign算法

[size=0.875]1


[size=0.875]2


[size=0.875]3


[size=0.875]4


[size=0.875]5


[size=0.875]6


[size=0.875]7


[size=0.875]8


[size=0.875]9


[size=0.875]10


[size=0.875]11


[size=0.875]12


[size=0.875][size=0.875]public static double copySign(double magnitude, double sign) {
[size=0.875]   if (magnitude == 0.0) return 0.0;
[size=0.875]   else if (sign < 0) {
[size=0.875]     if (magnitude < 0) return magnitude;
[size=0.875]     else return -magnitude;
[size=0.875]   }
[size=0.875]   else if (sign > 0) {
[size=0.875]     if (magnitude < 0) return -magnitude;
[size=0.875]     else return magnitude;
[size=0.875]   }
[size=0.875]   return magnitude;
[size=0.875]}




山不在高,有仙则名;水不在深,有龙则灵。
返回列表