【问题标题】:Math.random() and precision loss curiosityMath.random() 和精度损失好奇心
【发布时间】:2011-12-06 15:35:49
【问题描述】:

以下内容无法编译:

int result = Math.random() + 1;

error: possible loss of precision
    int result = Math.random() + 1;
                               ^
    required: int
    found:    double

但以下编译:

int result = 0;
result += Math.random() + 1;

为什么?

将可编译代码放入嵌套循环中,人们会期望每次迭代结果都会增加 1,因为 Math.random() 总是返回一个值小于 1 的双精度数,并且当添加到整数时,小数部分将是由于精度损失而丢失。运行以下代码,看到意外的结果:

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 &lt; 42000000; i ++)的奇怪方式
  • 先尝试将 Math.random() 调用转换为整数。目前,您的代码中的 '1' 文字被隐式转换为双精度数,这意味着在某处丢失了精度。
  • float vs. double precision 的可能重复项
  • @Woot4Moo 不是吗?整数的最大值(在 java 中)是 2,147,483,647。那是 4200 万的 50 倍。

标签: java math


【解决方案1】:

+= 这样的赋值运算符会进行隐式转换。

注意:在这种情况下,Math.random() 每次都会向下舍入为 0,这会大大降低精度。 ;)

但是,Math.random() + 1 被四舍五入到 2 的可能性很小。例如1.999999 将四舍五入为 1,但 1.9999999999999999 将四舍五入为 2(但 double + 运算符而不是转换为 int)。

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

打印

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被提升为double,然后加,然后截断。并不是随机数被预先截断。
  • 当我发现System.out.println(0.99999999999999999);1.0 时,你回答了。 +1 速度快。
  • @SeanOwen,我的意思是有一个错误的假设,即由于 Math.random() 总是四舍五入为 0,Math.random() + 1 总是四舍五入为 1。
  • 被舍入的不是 1.9999999999999999。而是 1.0D + 0.9999999999999999D = 2.0D。实际上它与 += 运算符无关。
  • @jsravn, += 正在转换为 int,否则程序将无法编译。我已经澄清了哪个操作进行了舍入。转换为 (int) 总是向下取整(如 Math.floor(x))。但是double 操作会四舍五入到最接近的表示。
【解决方案2】:

IEEE 数学实现的细节指出了精度损失和双精度/浮点到整数转换的不可靠结果。例如,我曾经发现比较浮点数的代码:

int x = 0;
if (a <= b) 
{ 
    x = y; 
}
if (a > b) 
{ 
    x = z; 
}

有时结果是x == 0,例如一个数字没有被if语句捕获,我不得不将代码重写为:

int x = 0; 
if (a <= b) 
{ 
    x = y; 
} 
else 
{ 
     x = z; 
}

【讨论】:

  • 我猜这是因为零已签名。 (即,在浮点系统中同时存在“+0”和“-0”,它们具有不同的表示形式)不过,看看这些数字如何与比较运算符交互有点有趣。
  • 是的,甚至有一些方法可以调整接近零的距离。原始代码是由一位科学家编写的,所以我不得不向他解释 IEEE 数学如何违反代数表达式的一些基本原则。
  • 虽然这并不能回答我的两个问题,但它非常有趣和有用!谢谢!
  • 我想让您了解浮点数学的一般问题。 Java 隐藏了 IEEE 数学的许多细节,但没有隐藏后果。
【解决方案3】:

根据定义 Math.random() 返回 double 结果从 0.0 到 1.0。操作Math.random() + 1 创建双精度结果,然后分配给 int 变量,产生整数结果。在每次迭代中,结果都是 1,除非 Math.random() 正好返回 1.0。它发生的机会非常低,但仍然存在。从统计上看,它似乎是 1/6000。这就是某些循环迭代将 2 添加到结果中的原因。

所以,这里没有丢失精度。一切都按照规范发生。

【讨论】:

  • 这个答案是错误的。根据规范,Math.random 返回一个双精度值,即 '0
猜你喜欢
  • 1970-01-01
  • 2018-07-26
  • 1970-01-01
  • 2011-03-21
  • 2014-07-13
  • 1970-01-01
  • 2020-07-28
  • 2015-04-26
相关资源
最近更新 更多