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

时间:2015-02-12 09:55:35

标签: floating-point double floating-accuracy ieee-754

在以下示例中是否可以将除以0(或无穷大)?

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

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

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

12 个答案:

答案 0 :(得分:131)

在Java中,如果a - b0永远不会等于a != b。这是因为Java要求支持非规范化数字的IEEE 754浮点运算。来自spec

  

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

如果FPUdenormalized numbers一起使用,减去不相等的数字永远不会产生零(与乘法不同),另请参阅this question

对于其他语言,它取决于。例如,在C或C ++中,IEEE 754支持是可选的。

也就是说it is possible表达式2 / (a - b)溢出,例如a = 5e-308b = 4e-308

答案 1 :(得分:51)

作为一种解决方法,以下是什么?

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

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

答案 2 :(得分:25)

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

现在,a == b返回true的唯一方法是ab包含完全相同的位。如果它们仅由最低有效位区别,则它们之间的差异将不为0.

编辑:

正如芭丝谢芭正确评论的那样,有一些例外:

  1. “不是数字比较”错误与自身相同但会有相同的位模式。

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

  3. 因此,如果ab都是Double.NaN,您将会到达else子句,但由于NaN - NaN也会返回NaN,您将不会除以零。

答案 3 :(得分:17)

这里不存在除零的情况。

SMT Solver Z3支持精确的IEEE浮点运算。让我们让Z3找到ab这样的a != 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选择任意舍入模式(rm)。这意味着结果适用于所有可能的舍入模式(其中有五个)。结果还包括游戏中的任何变量可能是NaN或无穷大的可能性。

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

如果使用按位相等性实现了相等性测试,则+0f-0f将成为使a - 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 float问题很好,因为很难忽略案例(例如NaN-0f+-inf),你可以提出任意问题。无需解释和引用规范。您甚至可以询问混合浮点数和整数问题,例如“这个特定的int log2(float)算法是否正确?”。

答案 4 :(得分:12)

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

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

当除法结果大到存储在double中时,即使分母非零,也会返回无穷大。

答案 5 :(得分:6)

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

有效处理非规范化浮点格式的电路非常昂贵,并非所有处理器都包含它。有些处理器可以选择在非常小的数字上进行操作比在其他值上进行操作慢得多,或者让处理器简单地将对于规范化格式而言太小的数字视为零。

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

答案 6 :(得分:5)

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

答案 7 :(得分:2)

我可以想到可能能够导致这种情况发生的情况。这是基数10中的类似样本 - 当然,这会发生在基数2中。

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

想象一下,为方便起见,我们有一个浮点单元,它在10号基数上运行,精度为3位数。从10.0减去9.99时会发生什么?

1.00e2-9.99e1

转换为每个值赋予相同的指数

1.00e2-0.999e2

舍入到3位

1.00e2-1.00e2

哦,哦!

这是否最终取决于FPU设计。由于double的指数范围非常大,硬件必须在某个时刻在内部进行舍入,但在上面的情况下,内部只需要一个额外的数字就可以防止出现任何问题。

答案 8 :(得分:1)

你不应该比较浮动或双打的平等;因为,你无法保证你指定给float或double的数字是准确的。

为了比较浮动的平等性,您需要检查值是否足够接近"达到相同的价值:

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

答案 9 :(得分:1)

基于@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,这是实现分裂的最佳方式。

答案 10 :(得分:1)

除以零是不确定的,因为正数的极限趋于无穷大,负数的限制倾向于负无穷大。

由于没有语言标记,因此不确定这是C ++还是Java。

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

         return Double.NaN; // Java
     }
     else
     {
         return 2 / (a - b);
     }
}

答案 11 :(得分:1)

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

所以a == b不能用a和b的任何double值来完成,当a = 0.333和b = 1/3时,如何处理a == b?根据您的操作系统与FPU与数字与语言的比较而不是0之后的数字3,您将拥有真或假。

无论如何,如果你在计算机上进行“双值计算”,你必须处理准确性,所以不必做a==b,你必须做absolute_value(a-b)<epsilon,而epsilon与你的相关在你的算法中进行建模。所有双重比较都不能有epsilon值。

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

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