Math.random() and precision loss curiosity
以下内容无法编译:
1 2 3 4 5 6 7
| int result = Math. random() + 1;
error : possible loss of precision
int result = Math. random() + 1;
^
required : int
found : double |
但是以下内容会编译:
1 2
| int result = 0;
result += Math. random() + 1; |
为什么?
将可编译代码放入嵌套循环中,由于Math.random()始终返回值小于1的double,并且将其添加到整数时,小数部分会由于 精度损失。 运行以下代码,并查看意外的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class MathRandomCuriosity
{
public static void main (String[] args )
{
int result = 0;
for (int i = 0; i < 10; i ++)
{
// System.out.println(result);
for (int j = 0; j < 20; j ++)
{
// System.out.println(result);
for (int k = 0; k < 300; k ++)
{
// System.out.println(result);
for (int m = 0; m < 7000; m ++)
{
result += Math. random() + 1;
}
}
}
}
System. out. println(result );
}
} |
如果10 * 20 * 300 * 7000 = 42,000,000次迭代,结果应为42,000,000。 但这不是! 结果有所不同,即42,000,007与42,000,006与42,000,010等。
为什么?
顺便说一下...这不是在任何地方使用的代码,它来自我在时事通讯中收到的测验。 嵌套循环的原因是使我可以间隔查看结果的值。
-
在一般情况下,这个问题已经被反复回答。
-
编写for (int i = 0; i < 42000000; i ++)的方式很奇怪
-
尝试首先将Math.random()调用强制转换为整数。当前,代码中的1文字被隐式转换为双精度,这意味着在某处会丢失精度。
-
浮点数与双精度可能重复
-
@ Woot4Moo不?整数最大值(在Java中)为2,147,483,647。那就是4200万的50倍。
-
Math根据JVM实现调用系统函数:"与StrictMath类的某些数字方法不同,Math类的等效函数的所有实现未定义为返回逐位相同的结果"。因此,出现了一个问题:您正在使用哪种JVM / OS? ((Math.random() + 1) == 2)在最内层的循环中是否一直为true?
-
@ Woot4Moo详细说明,Javas int是一个带符号的32位整数,因此它的最大值约为2 ^ 31 = 20亿。
-
@RichardJ bah我知道前面有2个:(
-
@Viruzzo我现在确实确实看到了这一点,这就是为什么我需要在中午之前保持关闭。
-
我创建的嵌套循环是为了定期查看结果的值。
-
变化行为的可能重复,可能会导致精度损失
像+=这样的分配运算符进行隐式强制转换。
注意:在这种情况下,Math.random()每次都将四舍五入为0,这将大大降低精度。 ;)
但是Math.random() + 1被四舍五入的可能性很小。 1.999999将四舍五入为1,但1.9999999999999999将四舍五入为2(但double +运算符而不是强制转换为int)。
1 2 3 4
| long l = Double. doubleToLongBits(1.0);
double d0_999etc = Double. longBitsToDouble(l -1);
System. out. println("The value before 1 is" +d0_999etc +" cast to (int) is"+ (int) d0_999etc );
System. out. println("The value before 1, plus 1 is" +(1+d0_999etc )+" cast to (int) is"+(int)(1 +d0_999etc )); |
版画
1 2
| The value before 1 is 0.9999999999999999 cast to (int) is 0
The value before 1, plus 1 is 2.0 cast to (int) is 2 |
-
很小的附录:不是Math.random()被截断为0,而是" Math.random()+ 1"被截断为1。1被提升为两倍,然后加法,然后被截断。不是随机数被截断了。
-
当我发现System.out.println(0.99999999999999999);是1.0时,您回答了。 +1代表快速。
-
@SeanOwen,我的观点是有一个错误的假设,因为Math.random()总是四舍五入,所以Math.random() + 1总是四舍五入。
-
四舍五入不是1.9999999999999999。而是1.0D + 0.9999999999999999D = 2.0D。实际上,它与+ =运算符无关。
-
@jsravn,+=正在强制转换为int,否则该程序将无法编译。我已经弄清楚了舍入是哪个操作。强制转换为(int)总是四舍五入(例如Math.floor(x))。但是,double操作会四舍五入到最接近的表示形式。
-
因此,我的第一个问题的答案仍然不清楚。 @Peter我知道像+ =这样的赋值运算符会执行隐式强制转换,但不是=也会执行隐式强制转换吗?为什么编译" + ="代码而不编译" ="代码示例?
-
@PeterLawrey感谢您对第二个问题的简洁回答,包括示例代码。这是非常有帮助的。
-
@PatrickGarner,当=用于初始化时,使用隐式强制转换,但是,当=用于赋值时,不执行强制转换。
-
确实,我最初的主张是错误的。 + =确实与此有关-也就是说,随着结果幅度的增加,浮点加法将增加到x + 2的可能性增加(因为x + = m变成x =(int)((double) x + m))。
IEEE数学实现的详细信息指出了从双精度浮点数到整数转换的精度和不可靠结果的损失。例如,我曾经找到比较浮点数的代码:
1 2 3 4 5 6 7 8 9
| int x = 0;
if (a <= b)
{
x = y;
}
if (a > b)
{
x = z;
} |
有时结果是x == 0,例如两个if语句都没有捕获到的数字,我不得不将代码重写为:
1 2 3 4 5 6 7 8 9
| int x = 0;
if (a <= b)
{
x = y;
}
else
{
x = z;
} |
-
我猜这是因为零是有符号的。 (即,在浮点系统中同时存在" +0"和" -0",它们具有不同的表示形式)有趣的是,这些数字如何与比较运算符进行交互。
-
是的,甚至还有一些方法可以将零舍入的次数调整为零。原始代码是由一位科学家编写的,因此我不得不向他解释IEEE数学如何违反代数表达式的一些基本租户。
-
尽管这不能回答我的两个问题,但它非常有趣且有用!谢谢!
-
我想让您了解浮点数学的一般问题。 Java隐藏了IEEE数学的许多细节,但没有隐藏后果。
根据定义,Math.random()返回0.0至1.0的double结果。操作Math.random() + 1创建双精度结果,然后将其分配给int变量,该变量产生整数结果。除非Math.random()精确返回1.0,否则每次迭代的结果均为1。它发生的机会很小,但仍然存在。从统计上看,它大约是1/6000。这就是某些循环迭代将结果加2的原因。
因此,在这里不会失去精度。一切都按照规范进行。
-
这个答案是错误的。根据规范,Math.random返回一个双精度值,即0 <= value <1。因此,它不能精确返回1.0。