提问人:Thirler 提问时间:2/12/2015 最后编辑:Graham BorlandThirler 更新时间:3/9/2016 访问量:11286
是否可以通过减去两个不相等的浮点数得到 0?
Is it possible to get 0 by subtracting two unequal floating point numbers?
问:
在以下示例中,是否可以得到 0(或无穷大)的除法?
public double calculation(double a, double b)
{
if (a == b)
{
return 0;
}
else
{
return 2 / (a - b);
}
}
当然,在正常情况下,它不会。但是,如果 并且非常接近,会导致由于计算的精确性而导致呢?a
b
(a-b)
0
请注意,这个问题是针对 Java 的,但我认为它适用于大多数编程语言。
答:
无论 的值如何,您都不会得到除以零,因为浮点除以 0 不会引发异常。它返回无穷大。a - b
现在,返回 true 的唯一方法是 if 和 包含完全相同的位。如果它们仅相差最低有效位,则它们之间的差值将不会为 0。a == b
a
b
编辑:
正如拔示巴正确评论的那样,有一些例外:
“没有一个数字比较”假与自身,但将具有相同的位模式。
定义 -0.0 是为了将 true 与 +0.0 进行比较,并且它们的位模式不同。
因此,如果两者都是 ,您将到达 else 子句,但由于 also 返回 ,您将不会除以零。a
b
Double.NaN
NaN - NaN
NaN
评论
你永远不应该为了相等而比较浮点数或双打数;因为,你不能真正保证你分配给浮点数或双精度的数字是准确的。
要理智地比较浮点数的相等性,您需要检查该值是否“足够接近”相同的值:
if ((first >= second - error) || (first <= second + error)
评论
abs(first - second) < error
<= error
在 Java 中,永远不等于 if 。这是因为 Java 强制要求支持非规范化数字的 IEEE 754 浮点运算。从规格:a - b
0
a != b
特别是,Java 编程语言需要支持 IEEE 754 非规范化浮点数和渐进下溢,这使得证明特定数值算法的理想属性变得更加容易。如果计算结果为非规范化数字,则浮点运算不会“刷新到零”。
如果 FPU 使用非规范化数字,则减去不相等的数字永远不会产生零(与乘法不同),另请参阅此问题。
对于其他语言,这要视情况而定。例如,在 C 或 C++ 中,IEEE 754 支持是可选的。
也就是说,表达式可能会溢出,例如 和 。2 / (a - b)
a = 5e-308
b = 4e-308
评论
(a,b) = (3,1)
2/(a-b) = 2/(3-1) = 2/2 = 1
作为解决方法,以下情况如何?
public double calculation(double a, double b) {
double c = a - b;
if (c == 0)
{
return 0;
}
else
{
return 2 / c;
}
}
这样,您就不依赖于任何语言的IEEE支持。
评论
a=b
0
0
0
1/x + 1
x=0
1
0
0
0
这里不会发生除以零的情况。
SMT Solver Z3 支持精确的 IEEE 浮点运算。让我们让 Z3 找到数字,这样:a
b
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
被实现为质量,以便和比较相等。与零的比较也是使用 As 实现的。由于该问题旨在避免除以零,因此这是适当的比较。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 浮点问题很好,因为它很难忽略情况(例如 、 、 ),并且您可以提出任意问题。无需解释和引用规范。您甚至可以提出浮点数和整数混合问题,例如“这个特定的算法正确吗?NaN
-0f
+-inf
int log2(float)
评论
我能想到一种情况,你可能会导致这种情况发生。这是以 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 位数字即可防止出现任何问题。
评论
strictfp
double
strictfp
a
b
double
在符合 IEEE-754 的浮点实现中,每种浮点类型都可以保存两种格式的数字。1(“归一化”)用于大多数浮点值,但它可以表示的第二小数字仅比最小的数字大一点点,因此它们之间的差异不能以相同的格式表示。另一种(“非规范化”)格式仅用于无法在第一种格式中表示的非常小的数字。
有效处理非规范化浮点格式的电路成本高昂,并非所有处理器都包含它。某些处理器提供了一种选择,要么对非常小的数字进行操作比对其他值的操作慢得多,要么让处理器将太小而无法规范化格式的数字视为零。
Java 规范意味着实现应该支持非规范化格式,即使在这样做会使代码运行速度变慢的机器上也是如此。另一方面,某些实现可能会提供选项来允许代码运行得更快,以换取对值的略微草率的处理,这些值对于大多数目的来说太小而无关紧要(在值太小而无关紧要的情况下,使用它们进行计算的时间是重要的计算的十倍,这可能会很烦人, 因此,在许多实际情况下,齐平到零比缓慢但准确的算术更有用)。
提供的函数确实可以返回无穷大:
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
当除法的结果要存储在双精度中时,即使分母不为零,也会返回无穷大。
根据@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 的最简单方法是实际执行除法。
在 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。
除以零是未定义的,因为正数的极限趋向于无穷大,负数的极限趋向于负无穷大。
不确定这是 C++ 还是 Java,因为没有语言标签。
double calculation(double a, double b)
{
if (a == b)
{
return nan(""); // C++
return Double.NaN; // Java
}
else
{
return 2 / (a - b);
}
}
核心问题是,当你有“太多”的十进制时,计算机表示双精度(又名浮点数,或数学语言中的实数)是错误的,例如当你处理不能写成数值的双精度时(pi 或 1/3 的结果)。
所以 a==b 不能用 a 和 b 的任何双精度值来做,当 a=0.333 和 b=1/3 时如何处理 a==b ?根据您的操作系统与 FPU、数字与语言与 0 后的 3 计数,您将有 true 或 false。
无论如何,如果你在计算机上进行“双精度计算”,你必须处理准确性,而不是做,你必须做,而 epsilon 是相对于你当时在算法中建模的内容。您不能为所有双重比较提供 epsilon 值。a==b
absolute_value(a-b)<epsilon
简而言之,当您键入 a==b 时,您有一个无法在计算机上翻译的数学表达式(对于任何浮点数)。
PS:哼,我在这里回答的一切或多或少都在其他人的回复和评论中。
评论