两个不相等的浮点数相减可以得到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 的,但我认为它适用于大多数编程语言。
无论 a - b
的值如何,您都不会被零除,因为浮点除以 0 不会引发异常。它 return 无穷大。
现在,如果 a
和 b
包含完全相同的位,那么 a == b
使 return 为真的唯一方法。如果它们仅相差最低有效位,则它们之间的差值不会为 0。
编辑:
正如 Bathsheba 正确评论的那样,有一些例外:
"Not a number compares" 自身为假,但具有相同的位模式。
-0.0定义为与+0.0比较true,它们的位模式不同。
因此,如果 a
和 b
都是 Double.NaN
,您将到达 else 子句,但由于 NaN - NaN
也 returns NaN
, 你不会被零除。
永远不要比较浮点数或双精度数是否相等;因为,您不能真正保证分配给 float 或 double 的数字是准确的。
要理智地比较浮点数是否相等,您需要检查值 "close enough" 是否与相同的值:
if ((first >= second - error) || (first <= second + error)
在 Java 中,如果 a != b
,a - b
永远不会等于 0
。这是因为 Java 要求支持非规范化数字的 IEEE 754 浮点运算。来自 spec:
In particular, the Java programming language requires support of IEEE 754 denormalized floating-point numbers and gradual underflow, which make it easier to prove desirable properties of particular numerical algorithms. Floating-point operations do not "flush to zero" if the calculated result is a denormalized number.
如果 FPU works with denormalized numbers, subtracting unequal numbers can never produce zero (unlike multiplication), also see this question.
对于其他语言,视情况而定。例如,在 C 或 C++ 中,IEEE 754 支持是可选的。
也就是说,it is possible 表达式 2 / (a - b)
溢出,例如 a = 5e-308
和 b = 4e-308
。
作为变通方法,以下情况如何?
public double calculation(double a, double b) {
double c = a - b;
if (c == 0)
{
return 0;
}
else
{
return 2 / c;
}
}
这样您就不会依赖任何语言的 IEEE 支持。
这里不可能被零除。
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
实现为 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
),并且您可以提出任意问题。无需解释和引用规范。您甚至可以提出混合浮点数和整数的问题,例如 "is this particular int log2(float)
algorithm correct?".
我可以想到您 可能 能够导致这种情况发生的情况。这是一个以 10 为基数的类似示例 - 实际上,这当然会发生在以 2 为基数的情况下。
浮点数或多或少以科学记数法存储 - 也就是说,存储的数字不是 35.2,而是更像 3.52e2。
为了方便起见,假设我们有一个以 10 为基数运算且精度为 3 位的浮点单元。从 10.0 减去 9.99 会发生什么?
1.00e2-9.99e1
移位使每个值具有相同的指数
1.00e2-0.999e2
四舍五入到 3 位数
1.00e2-1.00e2
呃哦!
这是否会发生最终取决于 FPU 设计。由于 double 的指数范围非常大,硬件在某些时候必须在内部舍入,但在上述情况下,内部只需多出 1 个数字就可以防止出现任何问题。
在符合 IEEE-754 的浮点实现中,每个浮点类型可以保存两种格式的数字。一 ("normalized") 用于大多数浮点值,但它可以表示的第二小的数字仅比最小的大一点点,因此它们之间的差异无法以相同的格式表示。另一种 ("denormalized") 格式仅用于第一种格式无法表示的非常小的数字。
有效处理非规范化浮点格式的电路非常昂贵,而且并非所有处理器都包含它。一些处理器提供了一个选择,要么对非常小的数字进行操作比对其他值的操作慢很多,要么让处理器简单地将对于规范化格式来说太小的数字视为零。
Java 规范暗示实现应该支持非规范化格式,即使在这样做会使代码 运行 变慢的机器上也是如此。另一方面,某些实现可能会提供允许代码更快 运行 的选项,以换取对值的稍微草率的处理,这些值对于大多数目的来说太小而不重要(在值太小的情况下重要的是,用它们进行计算的时间是真正重要的计算时间的十倍,这可能很烦人,因此在许多实际情况下,清零比缓慢但准确的算术更有用。
提供的函数确实可以return无穷大:
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
。
当除法的结果太大而无法存储在双精度数中时,即使分母不为零,也会return计算无穷大。
根据@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 可以切换到不 return 非规范化数字但用 0 替换它们的模式。在该模式下,如果 a 和 b 是微小的规范化数字,其中差异小于最小归一化数但大于 0,a != b 也不能保证 a == b。
"Never compare floating-point numbers" 是货物崇拜编程。在拥有咒语 "you need an 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);
}
}
核心问题是,当您使用 "too much" 小数时,双精度(又名浮点数,或数学语言中的实数)的计算机表示是错误的,例如,当您处理不能写为数值(pi 或 1/3 的结果)。
所以 a==b 不能用 a 和 b 的任何双精度值来完成,当 a=0.333 和 b=1/3 时,你如何处理 a==b?根据你的 OS vs FPU vs number vs language vs count of 3 after 0,你会有 true 或 false。
无论如何,如果你在电脑上做"double value calculation",你必须处理准确性,所以而不是做a==b
,你必须做absolute_value(a-b)<epsilon
,epsilon是相对于您当时在算法中建模的内容。您不能对所有双重比较都使用 epsilon 值。
简而言之,当您键入 a==b 时,您将得到一个无法在计算机上翻译的数学表达式(对于任何浮点数)。
PS: 嗯,我在这里回答的都或多或少在其他人的回复和评论中。
在下面的例子中是否可以除以 0(或无穷大)?
public double calculation(double a, double b)
{
if (a == b)
{
return 0;
}
else
{
return 2 / (a - b);
}
}
一般情况下当然不会。但是如果 a
和 b
非常接近,那么 (a-b)
会因为计算的精度而导致 0
吗?
请注意,这个问题是针对 Java 的,但我认为它适用于大多数编程语言。
无论 a - b
的值如何,您都不会被零除,因为浮点除以 0 不会引发异常。它 return 无穷大。
现在,如果 a
和 b
包含完全相同的位,那么 a == b
使 return 为真的唯一方法。如果它们仅相差最低有效位,则它们之间的差值不会为 0。
编辑:
正如 Bathsheba 正确评论的那样,有一些例外:
"Not a number compares" 自身为假,但具有相同的位模式。
-0.0定义为与+0.0比较true,它们的位模式不同。
因此,如果 a
和 b
都是 Double.NaN
,您将到达 else 子句,但由于 NaN - NaN
也 returns NaN
, 你不会被零除。
永远不要比较浮点数或双精度数是否相等;因为,您不能真正保证分配给 float 或 double 的数字是准确的。
要理智地比较浮点数是否相等,您需要检查值 "close enough" 是否与相同的值:
if ((first >= second - error) || (first <= second + error)
在 Java 中,如果 a != b
,a - b
永远不会等于 0
。这是因为 Java 要求支持非规范化数字的 IEEE 754 浮点运算。来自 spec:
In particular, the Java programming language requires support of IEEE 754 denormalized floating-point numbers and gradual underflow, which make it easier to prove desirable properties of particular numerical algorithms. Floating-point operations do not "flush to zero" if the calculated result is a denormalized number.
如果 FPU works with denormalized numbers, subtracting unequal numbers can never produce zero (unlike multiplication), also see this question.
对于其他语言,视情况而定。例如,在 C 或 C++ 中,IEEE 754 支持是可选的。
也就是说,it is possible 表达式 2 / (a - b)
溢出,例如 a = 5e-308
和 b = 4e-308
。
作为变通方法,以下情况如何?
public double calculation(double a, double b) {
double c = a - b;
if (c == 0)
{
return 0;
}
else
{
return 2 / c;
}
}
这样您就不会依赖任何语言的 IEEE 支持。
这里不可能被零除。
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
实现为 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
),并且您可以提出任意问题。无需解释和引用规范。您甚至可以提出混合浮点数和整数的问题,例如 "is this particular int log2(float)
algorithm correct?".
我可以想到您 可能 能够导致这种情况发生的情况。这是一个以 10 为基数的类似示例 - 实际上,这当然会发生在以 2 为基数的情况下。
浮点数或多或少以科学记数法存储 - 也就是说,存储的数字不是 35.2,而是更像 3.52e2。
为了方便起见,假设我们有一个以 10 为基数运算且精度为 3 位的浮点单元。从 10.0 减去 9.99 会发生什么?
1.00e2-9.99e1
移位使每个值具有相同的指数
1.00e2-0.999e2
四舍五入到 3 位数
1.00e2-1.00e2
呃哦!
这是否会发生最终取决于 FPU 设计。由于 double 的指数范围非常大,硬件在某些时候必须在内部舍入,但在上述情况下,内部只需多出 1 个数字就可以防止出现任何问题。
在符合 IEEE-754 的浮点实现中,每个浮点类型可以保存两种格式的数字。一 ("normalized") 用于大多数浮点值,但它可以表示的第二小的数字仅比最小的大一点点,因此它们之间的差异无法以相同的格式表示。另一种 ("denormalized") 格式仅用于第一种格式无法表示的非常小的数字。
有效处理非规范化浮点格式的电路非常昂贵,而且并非所有处理器都包含它。一些处理器提供了一个选择,要么对非常小的数字进行操作比对其他值的操作慢很多,要么让处理器简单地将对于规范化格式来说太小的数字视为零。
Java 规范暗示实现应该支持非规范化格式,即使在这样做会使代码 运行 变慢的机器上也是如此。另一方面,某些实现可能会提供允许代码更快 运行 的选项,以换取对值的稍微草率的处理,这些值对于大多数目的来说太小而不重要(在值太小的情况下重要的是,用它们进行计算的时间是真正重要的计算时间的十倍,这可能很烦人,因此在许多实际情况下,清零比缓慢但准确的算术更有用。
提供的函数确实可以return无穷大:
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
。
当除法的结果太大而无法存储在双精度数中时,即使分母不为零,也会return计算无穷大。
根据@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 可以切换到不 return 非规范化数字但用 0 替换它们的模式。在该模式下,如果 a 和 b 是微小的规范化数字,其中差异小于最小归一化数但大于 0,a != b 也不能保证 a == b。
"Never compare floating-point numbers" 是货物崇拜编程。在拥有咒语 "you need an 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);
}
}
核心问题是,当您使用 "too much" 小数时,双精度(又名浮点数,或数学语言中的实数)的计算机表示是错误的,例如,当您处理不能写为数值(pi 或 1/3 的结果)。
所以 a==b 不能用 a 和 b 的任何双精度值来完成,当 a=0.333 和 b=1/3 时,你如何处理 a==b?根据你的 OS vs FPU vs number vs language vs count of 3 after 0,你会有 true 或 false。
无论如何,如果你在电脑上做"double value calculation",你必须处理准确性,所以而不是做a==b
,你必须做absolute_value(a-b)<epsilon
,epsilon是相对于您当时在算法中建模的内容。您不能对所有双重比较都使用 epsilon 值。
简而言之,当您键入 a==b 时,您将得到一个无法在计算机上翻译的数学表达式(对于任何浮点数)。
PS: 嗯,我在这里回答的都或多或少在其他人的回复和评论中。