浮点数除以零的行为
The behaviour of floating point division by zero
考虑
#include <iostream>
int main()
{
double a = 1.0 / 0;
double b = -1.0 / 0;
double c = 0.0 / 0;
std::cout << a << b << c; // to stop compilers from optimising out the code.
}
一直以为a
会是+Inf,b
会是-Inf,c
会是NaN。但我也听到谣言,严格来说,浮点数除以零的行为是 undefined,因此上面的代码不能被认为是可移植的 C++。 (这在理论上抹杀了我的百万行加上代码堆栈的完整性。糟糕。)
谁是正确的?
请注意,我对 实施定义 很满意,但我在这里谈论的是吃猫、恶魔打喷嚏 未定义的行为 .
C++ 标准不强制 IEEE 754 标准,因为它主要取决于硬件架构。
如果 hardware/compiler 正确实施 IEEE 754 标准,该部门将提供预期的 INF、-INF 和 NaN,否则...这取决于。
未定义意味着,编译器实现决定,并且有很多变量,如硬件架构、代码生成效率、编译器开发人员的懒惰等。
来源:
C++ 标准声明除以 0.0 是 undefined
C++ Standard 5.6.4
... If the second operand of / or % is zero the behavior is undefined
C++ Standard 18.3.2.4
...static constexpr bool is_iec559;
...56. True if and only if the type adheres to IEC 559 standard.217
...57. Meaningful for all floating point types.
IEEE754的C++检测:
标准库包含检测是否支持 IEEE754 的模板:
static constexpr bool is_iec559;
#include <numeric>
bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();
不支持IEEE754怎么办?
视情况而定,通常除以 0 会触发硬件异常并使应用程序终止。
除以 0 是 未定义的行为。
来自 C++ standard (C++11) 的第 5.6 节:
The binary /
operator yields the quotient, and the binary %
operator
yields the remainder from the division of the first expression by the
second. If the second operand of /
or %
is zero the behavior is
undefined. For integral operands the /
operator yields the algebraic
quotient with any fractional part discarded; if the quotient a/b
is
representable in the type of the result, (a/b)*b + a%b
is equal to a
.
/
运算符的整数和浮点操作数之间没有区别。该标准仅声明除以零是未定义的,而不考虑操作数。
引用 cppreference:
If the second operand is zero, the behavior is undefined, except that if floating-point division is taking place and the type supports IEEE floating-point arithmetic (see std::numeric_limits::is_iec559
), then:
if one operand is NaN, the result is NaN
dividing a non-zero number by ±0.0 gives the correctly-signed infinity and FE_DIVBYZERO
is raised
dividing 0.0 by 0.0 gives NaN and FE_INVALID
is raised
这里说的是浮点除法,所以double
除以0是否未定义实际上是实现定义的。
如果 std::numeric_limits<double>::is_iec559
是 true
,并且是 "usually true
",则行为定义明确并产生预期结果。
一个非常安全的选择是扑通一声:
static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");
...在您的代码附近。
在 [expr]/4 中我们有
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined. [ Note: most existing implementations of C++ ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines, and is usually adjustable by a library function. —end note ]
强调我的
所以根据标准,这是未定义的行为。它确实继续说其中一些情况实际上由实现处理并且是可配置的。所以它不会说它是实现定义的,但它确实让你知道实现确实定义了一些这种行为。
这也取决于浮点环境。
cppreference 有详细信息:
http://en.cppreference.com/w/cpp/numeric/fenv
(虽然没有例子)。
这应该在大多数 desktop/server C++11 和 C99 环境中可用。在所有这些标准化之前,还有特定于平台的变体。
我希望启用异常会使代码 运行 变慢,所以可能由于这个原因,我知道的大多数平台默认情况下都禁用异常。
关于提交者的问题“谁是正确的?”,完全可以说 两个 答案都是正确的。 C 标准将行为描述为 'undefined' 的事实并没有规定底层硬件实际做什么;它仅仅意味着 如果您希望您的程序根据标准 有意义,您 - 可能不会假设 - 硬件实际上实现了该操作。但是如果你恰好运行在实现了IEEE标准的硬件上,你会发现这个操作实际上已经实现了,结果也是IEEE标准规定的。
整数和浮点数被零除都是未定义的行为[expr.mul]p4:
The binary / operator yields the quotient, and the binary % operator yields the remainder from the division
of the first expression by the second. If the second operand of / or % is zero the behavior is undefined. ...
虽然实现可以选择性地支持 Annex F,它具有明确定义的浮点数除以零的语义。
我们可以从这个 clang 错误报告中看到 clang sanitizer regards IEC 60559 floating-point division by zero as undefined 即使定义了宏 __STDC_IEC_559__,它也是由系统定义的 headers 并且至少对于 clang 不支持 Annex F 因此对于 clang 仍然是未定义的行为:
Annex F of the C standard (IEC 60559 / IEEE 754 support) defines the
floating-point division by zero, but clang (3.3 and 3.4 Debian snapshot)
regards it as undefined. This is incorrect:
对 Annex F 的支持是可选的,我们不支持它。
#if STDC_IEC_559
这个宏是由您的系统headers定义的,不是我们;这是
您系统中的错误 headers。 (FWIW,GCC 不完全支持 Annex
F 要么,IIRC,所以它甚至不是 Clang-specific 错误。)
该错误报告和其他两个错误报告 UBSan: Floating point division by zero is not undefined and clang should support Annex F of ISO C (IEC 60559 / IEEE 754) 表明 gcc 符合 Annex F 关于浮点除以零的要求。
Though I agree that it isn't up to the C library to define STDC_IEC_559 unconditionally, the problem is specific to clang. GCC does not fully support Annex F, but at least its intent is to support it by default and the division is well-defined with it if the rounding mode isn't changed. Nowadays not supporting IEEE 754 (at least the basic features like the handling of division by zero) is regarded as bad behavior.
这是 gcc Semantics of Floating Point Math in GCC wiki which indicates that -fno-signaling-nans is the default which agrees with the gcc optimizations options documentation 的进一步支持,它说:
The default is -fno-signaling-nans.
有趣的是 UBSan for clang defaults to including float-divide-by-zero under -fsanitize=undefined while gcc does not:
Detect floating-point division by zero. Unlike other similar options, -fsanitize=float-divide-by-zero is not enabled by -fsanitize=undefined, since floating-point division by zero can be a legitimate way of obtaining infinities and NaNs.
看到了live for clang and live for gcc.
考虑
#include <iostream>
int main()
{
double a = 1.0 / 0;
double b = -1.0 / 0;
double c = 0.0 / 0;
std::cout << a << b << c; // to stop compilers from optimising out the code.
}
一直以为a
会是+Inf,b
会是-Inf,c
会是NaN。但我也听到谣言,严格来说,浮点数除以零的行为是 undefined,因此上面的代码不能被认为是可移植的 C++。 (这在理论上抹杀了我的百万行加上代码堆栈的完整性。糟糕。)
谁是正确的?
请注意,我对 实施定义 很满意,但我在这里谈论的是吃猫、恶魔打喷嚏 未定义的行为 .
C++ 标准不强制 IEEE 754 标准,因为它主要取决于硬件架构。
如果 hardware/compiler 正确实施 IEEE 754 标准,该部门将提供预期的 INF、-INF 和 NaN,否则...这取决于。
未定义意味着,编译器实现决定,并且有很多变量,如硬件架构、代码生成效率、编译器开发人员的懒惰等。
来源:
C++ 标准声明除以 0.0 是 undefined
C++ Standard 5.6.4
... If the second operand of / or % is zero the behavior is undefined
C++ Standard 18.3.2.4
...static constexpr bool is_iec559;
...56. True if and only if the type adheres to IEC 559 standard.217
...57. Meaningful for all floating point types.
IEEE754的C++检测:
标准库包含检测是否支持 IEEE754 的模板:
static constexpr bool is_iec559;
#include <numeric>
bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();
不支持IEEE754怎么办?
视情况而定,通常除以 0 会触发硬件异常并使应用程序终止。
除以 0 是 未定义的行为。
来自 C++ standard (C++11) 的第 5.6 节:
The binary
/
operator yields the quotient, and the binary%
operator yields the remainder from the division of the first expression by the second. If the second operand of/
or%
is zero the behavior is undefined. For integral operands the/
operator yields the algebraic quotient with any fractional part discarded; if the quotienta/b
is representable in the type of the result,(a/b)*b + a%b
is equal toa
.
/
运算符的整数和浮点操作数之间没有区别。该标准仅声明除以零是未定义的,而不考虑操作数。
引用 cppreference:
If the second operand is zero, the behavior is undefined, except that if floating-point division is taking place and the type supports IEEE floating-point arithmetic (see
std::numeric_limits::is_iec559
), then:
if one operand is NaN, the result is NaN
dividing a non-zero number by ±0.0 gives the correctly-signed infinity and
FE_DIVBYZERO
is raiseddividing 0.0 by 0.0 gives NaN and
FE_INVALID
is raised
这里说的是浮点除法,所以double
除以0是否未定义实际上是实现定义的。
如果 std::numeric_limits<double>::is_iec559
是 true
,并且是 "usually true
",则行为定义明确并产生预期结果。
一个非常安全的选择是扑通一声:
static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");
...在您的代码附近。
在 [expr]/4 中我们有
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined. [ Note: most existing implementations of C++ ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines, and is usually adjustable by a library function. —end note ]
强调我的
所以根据标准,这是未定义的行为。它确实继续说其中一些情况实际上由实现处理并且是可配置的。所以它不会说它是实现定义的,但它确实让你知道实现确实定义了一些这种行为。
这也取决于浮点环境。
cppreference 有详细信息: http://en.cppreference.com/w/cpp/numeric/fenv (虽然没有例子)。
这应该在大多数 desktop/server C++11 和 C99 环境中可用。在所有这些标准化之前,还有特定于平台的变体。
我希望启用异常会使代码 运行 变慢,所以可能由于这个原因,我知道的大多数平台默认情况下都禁用异常。
关于提交者的问题“谁是正确的?”,完全可以说 两个 答案都是正确的。 C 标准将行为描述为 'undefined' 的事实并没有规定底层硬件实际做什么;它仅仅意味着 如果您希望您的程序根据标准 有意义,您 - 可能不会假设 - 硬件实际上实现了该操作。但是如果你恰好运行在实现了IEEE标准的硬件上,你会发现这个操作实际上已经实现了,结果也是IEEE标准规定的。
整数和浮点数被零除都是未定义的行为[expr.mul]p4:
The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined. ...
虽然实现可以选择性地支持 Annex F,它具有明确定义的浮点数除以零的语义。
我们可以从这个 clang 错误报告中看到 clang sanitizer regards IEC 60559 floating-point division by zero as undefined 即使定义了宏 __STDC_IEC_559__,它也是由系统定义的 headers 并且至少对于 clang 不支持 Annex F 因此对于 clang 仍然是未定义的行为:
Annex F of the C standard (IEC 60559 / IEEE 754 support) defines the floating-point division by zero, but clang (3.3 and 3.4 Debian snapshot) regards it as undefined. This is incorrect:
对 Annex F 的支持是可选的,我们不支持它。
#if STDC_IEC_559
这个宏是由您的系统headers定义的,不是我们;这是 您系统中的错误 headers。 (FWIW,GCC 不完全支持 Annex F 要么,IIRC,所以它甚至不是 Clang-specific 错误。)
该错误报告和其他两个错误报告 UBSan: Floating point division by zero is not undefined and clang should support Annex F of ISO C (IEC 60559 / IEEE 754) 表明 gcc 符合 Annex F 关于浮点除以零的要求。
Though I agree that it isn't up to the C library to define STDC_IEC_559 unconditionally, the problem is specific to clang. GCC does not fully support Annex F, but at least its intent is to support it by default and the division is well-defined with it if the rounding mode isn't changed. Nowadays not supporting IEEE 754 (at least the basic features like the handling of division by zero) is regarded as bad behavior.
这是 gcc Semantics of Floating Point Math in GCC wiki which indicates that -fno-signaling-nans is the default which agrees with the gcc optimizations options documentation 的进一步支持,它说:
The default is -fno-signaling-nans.
有趣的是 UBSan for clang defaults to including float-divide-by-zero under -fsanitize=undefined while gcc does not:
Detect floating-point division by zero. Unlike other similar options, -fsanitize=float-divide-by-zero is not enabled by -fsanitize=undefined, since floating-point division by zero can be a legitimate way of obtaining infinities and NaNs.
看到了live for clang and live for gcc.