在Java中,从double转换为int是如何工作的?

5

以下是Java代码:

double d = (double) m / n; //m and n are integers, n>0
int i = (int) (d * n);

i == m

最后的表达式总是真的吗? 如果不是,这个表达式总是真的吗?
i = (int) Math.round(d * n);

i == m

1
这不是一个愚蠢的问题;这个问题涉及到浮点运算和整数“可恢复性”的一些微妙问题。 - Nayuki
最近这个网站上有很多关于浮点数的问题 - 嗯... - user195488
4个回答

5

int i = (int) (d * n); i == m;

对于m=1,n=49,这是错误的。

i = (int) Math.round(d * n); i == m;

我的直觉告诉我应该是正确的,但严格证明可能很困难。


你甚至可以在JavaScript中验证我的说法。你可以在浏览器中输入以下内容:javascript: 1 / 49 * 49,这将得到0.9999...。 - Nayuki
2
+1 因为 double 具有 53 位精度,而且您将其除以和乘以小于 2^31 的数,结果的偏差应该小于 1/2^21 = 2 * 1/2^22(因为进行了两个操作,所以乘以 2)。 因此,舍入将具有很大的整数精度。 - starblue
我删掉了我的帖子,因为我认为我对第二种情况是错误的。很好的答案(我已经给了你我的+1)。 - MByD

4
您提出的第二个问题涉及Java中 ulp的大小。
如果ulp超过1 /(n),则舍入乘法将无法恢复原始的除以int。通常,较大的ulp与较大的double值相关联。与double关联的ulp在约9E15处开始超过1; 如果您恢复的double值在那里左右,那么您可能会发现round()无法获得预期的答案。但是,由于您正在使用int值,因此分母的最大值将是Integer.MAX_VALUE
以下程序测试所有正整数值的n,以查看哪个值在尝试恢复除以int时会导致最大的舍入误差潜力:
  public static void main(String[] args)
  {
    // start with large number
    int m = Integer.MAX_VALUE;
    double d = 0;

    double largestError = 0;
    int bigErrorCause = -1;
    for (int n = 1; n < Integer.MAX_VALUE; n++)
    {
      d = (double) m / n;
      double possibleError = Math.ulp(d) * n;
      if (possibleError > largestError)
      {
        largestError = possibleError;
        bigErrorCause = n;
      }
    }
    System.out.println("int " + bigErrorCause + " causes at most "
        + largestError + " error");
  }

输出结果为:

int 1073741823 最多会导致4.768371577590358E-7的误差

将其四舍五入使用Math.round,然后转换为int应该可以恢复原始int值。

1

从数学上讲,这应该是正确的。然而,由于浮点数舍入误差,它可能会变成错误的。你几乎永远不应该使用==来比较浮点精度数字。

最好使用类似这样的阈值进行比较:

Math.abs( d*n - m ) < 0.000001;

请注意,这两个语句应该是等效的。
i = (int) (d * n);
i = (int) Math.round(d * n);

然而,例如,如果d=3/2n=2,浮点误差可能导致i=2.999999999999,在截断/四舍五入后变为2。


3
你关于截断的推理很好。但是你的例子有问题,因为浮点数除以2始终是精确的(除了下溢)。;-) - Nayuki

1
第一个肯定不总是正确的。第二个我会说是正确的,但只因为我想不出反例。
如果n非常大,它可能是错误的,我真的不确定。但我知道它至少有99%的概率是正确的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接