为什么 Math class 中的 nextUp 方法会跳过一些值?
Why does nextUp method in Math class skips some values?
我只是想看看这个方法有什么作用。我创建了一个值为 3.14 的变量,只是因为它在那个时候出现在我的脑海中。
double n = 3.14;
System.out.println(Math.nextUp(n));
前面显示3.1400000000000006.
尝试使用 3.1400000000000001,显示相同。
尝试使用 333.33,显示 333.33000000000004。
对于许多其他值,它会显示适当的值,例如 73.6 结果为 73.60000000000001。
3.1400000000000000 和 3.1400000000000006 之间的值会发生什么变化?为什么它会跳过一些值?我知道与硬件相关的问题,但有时它工作正常。同样,即使知道无法进行精确操作,为什么库中包含这种方法?它看起来很没用,因为它并不总是正常工作。
Java 中一个有用的技巧是使用 new BigDecimal(double)
和 BigDecimal 的 toString
的精确性来显示双精度的精确值:
import java.math.BigDecimal;
public class Test {
public static void main(String[] args) {
System.out.println(new BigDecimal(3.14));
System.out.println(new BigDecimal(3.1400000000000001));
System.out.println(new BigDecimal(3.1400000000000006));
}
}
输出:
3.140000000000000124344978758017532527446746826171875
3.140000000000000124344978758017532527446746826171875
3.1400000000000005684341886080801486968994140625
双精度数是有限的,因此只有实数的特定子集是双精度数的精确值。当您创建双字面量时,您键入的十进制数由这些值中最接近的值表示。当您输出双精度数时,默认情况下,它显示为在输入时四舍五入到它的最短小数。您需要执行类似我在程序中使用的 BigDecimal 技术才能查看确切的值。
在这种情况下,3.14 和 3.1400000000000001 都比任何其他双倍更接近 3.140000000000000124344978758017532527446746826171875。上面下一个可精确表示的数字是 3.1400000000000005684341886080801486968994140625
就像@jgreve 提到的那样,由于在 java 中使用了 float 和 double 基元类型,这导致了所谓的舍入误差。另一方面,原始类型 int 是一个定点数,这意味着它能够 "fit" 在 32 位以内。双打不是定点的,这意味着双打计算的结果通常必须四舍五入以适应其有限表示,这有时会导致(如您的情况所示)不一致的值。
有关详细信息,请参阅以下两个链接。
https://en.wikipedia.org/wiki/Double-precision_floating-point_format
解决方法可能是以下两个,它为第一个双倍提供 "direction"。
double n = 1.4;
double x = 1.5;
System.out.println(Math.nextAfter(n, x));
或
double n = 1.4;
double next = n + Math.ulp(n);
System.out.println(next);
但是要处理浮点值,建议使用 BigDecimal
class
浮点数以二进制存储:十进制表示仅供人类使用。
使用 Rick Regan's decimal to floating point converter 3.14 转换为:
11.001000111101011100001010001111010111000010100011111
和 3.1400000000000006 转换为
11.0010001111010111000010100011110101110000101001
这确实是 53 位有效位的下一个二进制数。
我只是想看看这个方法有什么作用。我创建了一个值为 3.14 的变量,只是因为它在那个时候出现在我的脑海中。
double n = 3.14;
System.out.println(Math.nextUp(n));
前面显示3.1400000000000006.
尝试使用 3.1400000000000001,显示相同。
尝试使用 333.33,显示 333.33000000000004。
对于许多其他值,它会显示适当的值,例如 73.6 结果为 73.60000000000001。
3.1400000000000000 和 3.1400000000000006 之间的值会发生什么变化?为什么它会跳过一些值?我知道与硬件相关的问题,但有时它工作正常。同样,即使知道无法进行精确操作,为什么库中包含这种方法?它看起来很没用,因为它并不总是正常工作。
Java 中一个有用的技巧是使用 new BigDecimal(double)
和 BigDecimal 的 toString
的精确性来显示双精度的精确值:
import java.math.BigDecimal;
public class Test {
public static void main(String[] args) {
System.out.println(new BigDecimal(3.14));
System.out.println(new BigDecimal(3.1400000000000001));
System.out.println(new BigDecimal(3.1400000000000006));
}
}
输出:
3.140000000000000124344978758017532527446746826171875
3.140000000000000124344978758017532527446746826171875
3.1400000000000005684341886080801486968994140625
双精度数是有限的,因此只有实数的特定子集是双精度数的精确值。当您创建双字面量时,您键入的十进制数由这些值中最接近的值表示。当您输出双精度数时,默认情况下,它显示为在输入时四舍五入到它的最短小数。您需要执行类似我在程序中使用的 BigDecimal 技术才能查看确切的值。
在这种情况下,3.14 和 3.1400000000000001 都比任何其他双倍更接近 3.140000000000000124344978758017532527446746826171875。上面下一个可精确表示的数字是 3.1400000000000005684341886080801486968994140625
就像@jgreve 提到的那样,由于在 java 中使用了 float 和 double 基元类型,这导致了所谓的舍入误差。另一方面,原始类型 int 是一个定点数,这意味着它能够 "fit" 在 32 位以内。双打不是定点的,这意味着双打计算的结果通常必须四舍五入以适应其有限表示,这有时会导致(如您的情况所示)不一致的值。
有关详细信息,请参阅以下两个链接。
https://en.wikipedia.org/wiki/Double-precision_floating-point_format
解决方法可能是以下两个,它为第一个双倍提供 "direction"。
double n = 1.4;
double x = 1.5;
System.out.println(Math.nextAfter(n, x));
或
double n = 1.4;
double next = n + Math.ulp(n);
System.out.println(next);
但是要处理浮点值,建议使用 BigDecimal
class
浮点数以二进制存储:十进制表示仅供人类使用。
使用 Rick Regan's decimal to floating point converter 3.14 转换为:
11.001000111101011100001010001111010111000010100011111
和 3.1400000000000006 转换为
11.0010001111010111000010100011110101110000101001
这确实是 53 位有效位的下一个二进制数。