关于java:Math.random()和精度损失的好奇心

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等。

为什么?

顺便说一下...这不是在任何地方使用的代码,它来自我在时事通讯中收到的测验。 嵌套循环的原因是使我可以间隔查看结果的值。


+=这样的分配运算符进行隐式强制转换。

注意:在这种情况下,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


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;
}


根据定义,Math.random()返回0.0至1.0的double结果。操作Math.random() + 1创建双精度结果,然后将其分配给int变量,该变量产生整数结果。除非Math.random()精确返回1.0,否则每次迭代的结果均为1。它发生的机会很小,但仍然存在。从统计上看,它大约是1/6000。这就是某些循环迭代将结果加2的原因。

因此,在这里不会失去精度。一切都按照规范进行。