是否可以通过减去两个不相等的浮点数得到 0?

Is it possible to get 0 by subtracting two unequal floating point numbers?

提问人:Thirler 提问时间:2/12/2015 最后编辑:Graham BorlandThirler 更新时间:3/9/2016 访问量:11286

问:

在以下示例中,是否可以得到 0(或无穷大)的除法?

public double calculation(double a, double b)
{
     if (a == b)
     {
         return 0;
     }
     else
     {
         return 2 / (a - b);
     }
}

当然,在正常情况下,它不会。但是,如果 并且非常接近,会导致由于计算的精确性而导致呢?ab(a-b)0

请注意,这个问题是针对 Java 的,但我认为它适用于大多数编程语言。

浮精度 IEEE-754

评论

49赞 Thirler 2/12/2015
我必须尝试所有双打组合,这需要一段时间:)
3赞 Matt Clark 2/12/2015
对我来说,@Thirler听起来像是使用 JUnit 测试的时候了!
7赞 Thirler 2/12/2015
@bluebrain,我的猜测是你是你的文字数字 2.000 等包含许多小数点,由浮点数表示。因此,在比较中,最后一个不会由实际使用的数字表示。
4赞 guness 2/12/2015
@Thirler可能。“你不能真正保证你分配给浮点数或双精度的数字是准确的”
4赞 m0skit0 2/12/2015
请注意,在这种情况下返回 0 可能会导致难以调试的歧义,因此请确保您确实希望返回 0,而不是抛出异常或返回 NaN。

答:

25赞 Eran 2/12/2015 #1

无论 的值如何,您都不会得到除以零,因为浮点除以 0 不会引发异常。它返回无穷大。a - b

现在,返回 true 的唯一方法是 if 和 包含完全相同的位。如果它们仅相差最低有效位,则它们之间的差值将不会为 0。a == bab

编辑:

正如拔示巴正确评论的那样,有一些例外:

  1. “没有一个数字比较”假与自身,但将具有相同的位模式。

  2. 定义 -0.0 是为了将 true 与 +0.0 进行比较,并且它们的位模式不同。

因此,如果两者都是 ,您将到达 else 子句,但由于 also 返回 ,您将不会除以零。abDouble.NaNNaN - NaNNaN

评论

11赞 Bathsheba 2/12/2015
埃兰;严格来说并非如此。“没有一个数字比较”假与自身,但将具有相同的位模式。此外,-0.0 被定义为将 true 与 +0.0 进行比较,并且它们的位模式不同。
1赞 Eran 2/12/2015
@Bathsheba我没有考虑这些特殊情况。谢谢你的评论。
2赞 Thirler 2/12/2015
@Eran,除以 0 将在浮点数中返回无穷大。将其添加到问题中。
2赞 Eran 2/12/2015
@Prashant但在这种情况下不会发生除法,因为 a == b 将返回 true。
3赞 Voo 2/13/2015
实际上,您可以获得除以零的 FP 异常,这是 IEEE-754 标准定义的一个选项,尽管这可能不是大多数人所说的“例外”;)
1赞 aviad 2/12/2015 #2

你永远不应该为了相等而比较浮点数或双打数;因为,你不能真正保证你分配给浮点数或双精度的数字是准确的。

要理智地比较浮点数的相等性,您需要检查该值是否“足够接近”相同的值:

if ((first >= second - error) || (first <= second + error)

评论

6赞 Mark Pattison 2/12/2015
“不应该”有点强烈,但总的来说这是很好的建议。
1赞 glglgl 2/12/2015
虽然你是真的,但(或)更容易、更简洁。abs(first - second) < error<= error
3赞 milleniumbug 2/12/2015
虽然在大多数情况下(不是全部)是正确的,但并不能真正回答这个问题。
4赞 tmyklebu 2/13/2015
测试浮点数的相等性通常很有用。与未经仔细选择的 epsilon 进行比较是不理智的,当一个人测试是否相等时,与 epsilon 进行比较更不理智。
1赞 gnasher729 2/15/2015
如果你在浮点键上对数组进行排序,我可以保证,如果你尝试使用技巧将浮点数与epsilon进行比较,你的代码将不起作用。因为 a == b 和 b == c 意味着 a == c 的保证不再存在。对于哈希表,完全相同的问题。当相等不是传递的时,你的算法就会崩溃。
132赞 nwellnhof 2/12/2015 #3

在 Java 中,永远不等于 if 。这是因为 Java 强制要求支持非规范化数字的 IEEE 754 浮点运算。从规格a - b0a != b

特别是,Java 编程语言需要支持 IEEE 754 非规范化浮点数和渐进下溢,这使得证明特定数值算法的理想属性变得更加容易。如果计算结果为非规范化数字,则浮点运算不会“刷新到零”。

如果 FPU 使用非规范化数字,则减去不相等的数字永远不会产生零(与乘法不同),另请参阅此问题

对于其他语言,这要视情况而定。例如,在 C 或 C++ 中,IEEE 754 支持是可选的。

也就是说,表达式可能会溢出,例如 和 。2 / (a - b)a = 5e-308b = 4e-308

评论

4赞 Taemyr 2/12/2015
但是,OP 想知道 2/(a-b)。这能保证是有限的吗?
0赞 Thirler 2/12/2015
感谢您的回答,我添加了一个指向维基百科的链接,用于解释非规范化数字。
3赞 nwellnhof 2/12/2015
@Taemyr 查看我的编辑。分裂实际上可以溢出。
0赞 Cole Tobin 2/13/2015
@Taemyr => IEEE浮点是否如此,我不知道(a,b) = (3,1)2/(a-b) = 2/(3-1) = 2/2 = 1
1赞 nwellnhof 2/18/2015
@DrewDormann IEEE 754 对于 C99 也是可选的。参见该标准的附录 F。
50赞 malarres 2/12/2015 #4

作为解决方法,以下情况如何?

public double calculation(double a, double b) {
     double c = a - b;
     if (c == 0)
     {
         return 0;
     }
     else
     {
         return 2 / c;
     }
}

这样,您就不依赖于任何语言的IEEE支持。

评论

6赞 Joshua 2/13/2015
避免问题并一次性简化测试。我喜欢。
11赞 Cole Tobin 2/13/2015
-1 如果 ,则不应返回 。在 IEEE 754 中除以可以得到无穷大,也不例外。您正在避免该问题,因此返回是一个等待发生的错误。考虑。如果 ,则会导致 ,而不是正确的值:无穷大。a=b0001/x + 1x=01
5赞 Nick T 2/13/2015
@ColeJohnson正确答案也不是无穷大(除非您指定限制来自哪一侧,右侧 = +inf,左侧 = -inf,未指定 = 未定义或 NaN)。
12赞 slebetman 2/13/2015
@ChrisHayes:这是对这个问题的有效回答,认识到这个问题可能是一个XY问题:meta.stackexchange.com/questions/66377/what-is-the-xy-problem
17赞 jpmc26 2/14/2015
@ColeJohnson 返回并不是真正的问题。这就是 OP 在问题中的作用。您可以在块的该部分放置一个例外或任何适合情况的内容。如果你不喜欢回来,那应该批评这个问题。当然,像OP那样做并不能保证对答案投反对票。这个问题与给定函数完成后的进一步计算无关。据您所知,该程序的要求需要返回 .000
17赞 usr 2/12/2015 #5

这里不会发生除以零的情况。

SMT Solver Z3 支持精确的 IEEE 浮点运算。让我们让 Z3 找到数字,这样:aba != b && (a - b) == 0

(set-info :status unknown)
(set-logic QF_FP)
(declare-fun b () (FloatingPoint 8 24))
(declare-fun a () (FloatingPoint 8 24))
(declare-fun rm () RoundingMode)
(assert
(and (not (fp.eq a b)) (fp.eq (fp.sub rm a b) +zero) true))
(check-sat)

结果是 。没有这样的数字。UNSAT

上面的 SMTLIB 字符串还允许 Z3 选择任意舍入模式 ()。这意味着结果适用于所有可能的舍入模式(其中有五种)。结果还包括任何变量可能为或无穷大的可能性。rmNaN

a == b被实现为质量,以便和比较相等。与零的比较也是使用 As 实现的。由于该问题旨在避免除以零,因此这是适当的比较。fp.eq+0f-0ffp.eq

如果相等性测试是使用按位相等实现的,并且本来是一种使零的方法。此答案的先前版本不正确,其中包含有关该案例的模式详细信息,供好奇者使用。+0f-0fa - b

Z3 Online 尚不支持 FPA 理论。这个结果是使用最新的不稳定分支获得的。可以使用 .NET 绑定重现它,如下所示:

var fpSort = context.MkFPSort32();
var aExpr = (FPExpr)context.MkConst("a", fpSort);
var bExpr = (FPExpr)context.MkConst("b", fpSort);
var rmExpr = (FPRMExpr)context.MkConst("rm", context.MkFPRoundingModeSort());
var fpZero = context.MkFP(0f, fpSort);
var subExpr = context.MkFPSub(rmExpr, aExpr, bExpr);
var constraintExpr = context.MkAnd(
        context.MkNot(context.MkFPEq(aExpr, bExpr)),
        context.MkFPEq(subExpr, fpZero),
        context.MkTrue()
    );

var smtlibString = context.BenchmarkToSMTString(null, "QF_FP", null, null, new BoolExpr[0], constraintExpr);

var solver = context.MkSimpleSolver();
solver.Assert(constraintExpr);

var status = solver.Check();
Console.WriteLine(status);

使用 Z3 来回答 IEEE 浮点问题很好,因为它很难忽略情况(例如 、 、 ),并且您可以提出任意问题。无需解释和引用规范。您甚至可以提出浮点数和整数混合问题,例如“这个特定的算法正确吗?NaN-0f+-infint log2(float)

评论

0赞 A.L 2/15/2015
您能否添加SMT Solver Z3的链接和在线口译员的链接?虽然这个答案看起来完全合理,但有人可能会认为这些结果是错误的。
2赞 Keldor314 2/12/2015 #6

我能想到一种情况,你可能会导致这种情况发生。这是以 10 为基数的类似示例 - 实际上,这当然会发生在以 2 为基数。

浮点数或多或少地以科学记数法存储 - 也就是说,存储的数字更像是 3.52e2,而不是看到 35.2。

为了方便起见,想象一下,我们有一个以 10 为基数且精度为 3 位的浮点单元。从 9.99 中减去 10.0 会发生什么?

1.00e2-9.99e1

Shift 为每个值提供相同的指数

1.00e2-0.999e2

四舍五入到 3 位数字

1.00e2-1.00e2

呃哦!

这最终能否实现取决于 FPU 设计。由于双精度的指数范围非常大,因此硬件必须在某个时候在内部四舍五入,但在上述情况下,只需在内部多出 1 位数字即可防止出现任何问题。

评论

1赞 supercat 2/13/2015
保存对齐操作数以进行减法的寄存器需要保留额外的两个位,称为“保护位”,以处理这种情况。在减法将导致从最高有效位借用的情况下,要么较小的操作数的大小必须超过较大操作数的一半(意味着它只能有一个额外的精度),要么结果必须至少是较小操作数的大小的一半(意味着它只需要一个额外的位, 加上足以确保正确四舍五入的信息)。
1赞 Pascal Cuoq 2/13/2015
“这最终能否发生取决于FPU的设计” 不,它不会发生,因为Java定义说它不能发生。FPU设计与它没有任何关系。
0赞 supercat 2/14/2015
@PascalCuoq:如果我错了,请纠正我,但未启用,计算可能会产生太小但适合扩展精度浮点值的值。strictfpdouble
0赞 Pascal Cuoq 2/14/2015
@supercat 缺少只会影响“中间结果”的值,我引用的是 docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4。 并且是变量,而不是中间结果,因此它们的值是双精度值,因此是 2^-1074 的倍数。因此,这两个双精度值的减法是 2^-1074 的倍数,因此更宽的指数范围确实会改变差值为 0 iff a == b 的属性。strictfpabdouble
0赞 Keldor314 2/16/2015
@supercat 这是有道理的 - 你只需要一个额外的位来做到这一点。
6赞 supercat 2/13/2015 #7

在符合 IEEE-754 的浮点实现中,每种浮点类型都可以保存两种格式的数字。1(“归一化”)用于大多数浮点值,但它可以表示的第二小数字仅比最小的数字大一点点,因此它们之间的差异不能以相同的格式表示。另一种(“非规范化”)格式仅用于无法在第一种格式中表示的非常小的数字。

有效处理非规范化浮点格式的电路成本高昂,并非所有处理器都包含它。某些处理器提供了一种选择,要么对非常小的数字进行操作比对其他值的操作慢得多,要么让处理器将太小而无法规范化格式的数字视为零。

Java 规范意味着实现应该支持非规范化格式,即使在这样做会使代码运行速度变慢的机器上也是如此。另一方面,某些实现可能会提供选项来允许代码运行得更快,以换取对值的略微草率的处理,这些值对于大多数目的来说太小而无关紧要(在值太小而无关紧要的情况下,使用它们进行计算的时间是重要的计算的十倍,这可能会很烦人, 因此,在许多实际情况下,齐平到零比缓慢但准确的算术更有用)。

12赞 D Krueger 2/13/2015 #8

提供的函数确实可以返回无穷大:

public class Test {
    public static double calculation(double a, double b)
    {
         if (a == b)
         {
             return 0;
         }
         else
         {
             return 2 / (a - b);
         }
    }    

    /**
     * @param args
     */
    public static void main(String[] args) {
        double d1 = Double.MIN_VALUE;
        double d2 = 2.0 * Double.MIN_VALUE;
        System.out.println("Result: " + calculation(d1, d2)); 
    }
}

输出为 。Result: -Infinity

当除法的结果要存储在双精度中时,即使分母不为零,也会返回无穷大。

1赞 Orace 2/15/2015 #9

根据@malarres回复和@Taemyr评论,以下是我的小贡献:

public double calculation(double a, double b)
{
     double c = 2 / (a - b);

     // Should not have a big cost.
     if (isnan(c) || isinf(c))
     {
         return 0; // A 'whatever' value.
     }
     else
     {
         return c;
     }
}

我的观点是说:知道除法结果是 nan 还是 inf 的最简单方法是实际执行除法。

6赞 gnasher729 2/15/2015 #10

在 IEEE 754 之前的旧时代,a != b 很可能并不意味着 a-b != 0,反之亦然。这是最初创建IEEE 754的原因之一。

使用 IEEE 754,这几乎可以保证。允许 C 或 C++ 编译器以比所需精度更高的精度执行操作。因此,如果 a 和 b 不是变量而是表达式,那么 (a + b) != c 并不意味着 (a + b) - c != 0,因为 a + b 可以以更高的精度计算一次,而没有更高的精度可以计算一次。

许多 FPU 可以切换到一种模式,在该模式下,它们不返回非规范化数字,而是将其替换为 0。在该模式下,如果 a 和 b 是微小的归一化数,其差值小于最小归一化数但大于 0,则 a != b 也不能保证 a == b。

“永远不要比较浮点数”是货物崇拜的编程。在有“你需要一个 epsilon”的口头禅的人中,大多数人不知道如何正确选择那个 epsilon。

1赞 Khaled.K 2/18/2015 #11

除以零是未定义的,因为正数的极限趋向于无穷大,负数的极限趋向于负无穷大。

不确定这是 C++ 还是 Java,因为没有语言标签。

double calculation(double a, double b)
{
     if (a == b)
     {
         return nan(""); // C++

         return Double.NaN; // Java
     }
     else
     {
         return 2 / (a - b);
     }
}
1赞 Jean Davy 2/19/2015 #12

核心问题是,当你有“太多”的十进制时,计算机表示双精度(又名浮点数,或数学语言中的实数)是错误的,例如当你处理不能写成数值的双精度时(pi 或 1/3 的结果)。

所以 a==b 不能用 a 和 b 的任何双精度值来做,当 a=0.333 和 b=1/3 时如何处理 a==b ?根据您的操作系统与 FPU、数字与语言与 0 后的 3 计数,您将有 true 或 false。

无论如何,如果你在计算机上进行“双精度计算”,你必须处理准确性,而不是做,你必须做,而 epsilon 是相对于你当时在算法中建模的内容。您不能为所有双重比较提供 epsilon 值。a==babsolute_value(a-b)<epsilon

简而言之,当您键入 a==b 时,您有一个无法在计算机上翻译的数学表达式(对于任何浮点数)。

PS:哼,我在这里回答的一切或多或少都在其他人的回复和评论中。