Sta*_*nny 5 c++ floating-point language-lawyer
这里有几篇有关浮点数及其性质的文章。显然,必须始终谨慎比较浮点数和双精度数。要求平等的问题也已经讨论过,建议显然不要这样做。
但是,如果有直接分配,该怎么办:
double a = 5.4;
double b = a;
Run Code Online (Sandbox Code Playgroud)
假设a为任何非NaN值- a == b永远可以为假?
似乎答案显然不是,但我找不到在C ++环境中定义此行为的任何标准。IEEE-754声明具有相等(非NaN)位模式的两个浮点数相等。现在是否意味着我可以继续以这种方式比较我的双打而不必担心可维护性?我是否需要担心其他编译器/操作系统及其在这些方面的实现?还是编译器优化了一些位并破坏了它们的相等性?
我写了一个小程序,永久生成并比较非NaN随机双精度数-直到找到a == b产生yield 的情况false。我是否可以在将来随时随地编译/运行此代码,而不必期望暂停?(忽略字节序,并假定符号,指数和尾数位大小/位置保持不变)。
#include <iostream>
#include <random>
struct double_content {
std::uint64_t mantissa : 52;
std::uint64_t exponent : 11;
std::uint64_t sign : 1;
};
static_assert(sizeof(double) == sizeof(double_content), "must be equal");
void set_double(double& n, std::uint64_t sign, std::uint64_t exponent, std::uint64_t mantissa) {
double_content convert;
memcpy(&convert, &n, sizeof(double));
convert.sign = sign;
convert.exponent = exponent;
convert.mantissa = mantissa;
memcpy(&n, &convert, sizeof(double_content));
}
void print_double(double& n) {
double_content convert;
memcpy(&convert, &n, sizeof(double));
std::cout << "sign: " << convert.sign << ", exponent: " << convert.exponent << ", mantissa: " << convert.mantissa << " --- " << n << '\n';
}
int main() {
std::random_device rd;
std::mt19937_64 engine(rd());
std::uniform_int_distribution<std::uint64_t> mantissa_distribution(0ull, (1ull << 52) - 1);
std::uniform_int_distribution<std::uint64_t> exponent_distribution(0ull, (1ull << 11) - 1);
std::uniform_int_distribution<std::uint64_t> sign_distribution(0ull, 1ull);
double a = 0.0;
double b = 0.0;
bool found = false;
while (!found){
auto sign = sign_distribution(engine);
auto exponent = exponent_distribution(engine);
auto mantissa = mantissa_distribution(engine);
//re-assign exponent for NaN cases
if (mantissa) {
while (exponent == (1ull << 11) - 1) {
exponent = exponent_distribution(engine);
}
}
//force -0.0 to be 0.0
if (mantissa == 0u && exponent == 0u) {
sign = 0u;
}
set_double(a, sign, exponent, mantissa);
b = a;
//here could be more (unmodifying) code to delay the next comparison
if (b != a) { //not equal!
print_double(a);
print_double(b);
found = true;
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用Visual Studio Community 2017版本15.9.5
C ++标准在[basic.types]#3中明确指定:
对于任何平凡复制的类型
T,如果两个指针来T指向不同的T对象obj1和obj2,其中既不obj1也不obj2是一个潜在的重叠子对象,如果底层字节([intro.memory])构成obj1被复制到obj2,obj2随后应保持相同的值obj1。
它给出了这个例子:
T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p
Run Code Online (Sandbox Code Playgroud)
剩下的问题是a value是什么。我们在[basic.fundamental]#12(强调我的)中发现:
有三个浮点类型:
float,double,和long double。该类型double至少提供与相同的精度float,而该类型long double至少提供与一样的精度double。该类型的值float集是该类型的值集的子集double;该类型的值double集是该类型的值集的子集long double。 浮点类型的值表示形式是实现定义的。
由于C ++标准对浮点值的表示方式没有任何进一步的要求,因此,这是从该标准中得到的保证,因为只需要赋值即可保留值([expr.ass]#2):
在简单赋值(
=)中,通过用右操作数的结果替换其值来修改左操作数所引用的对象。
正如您正确观察到的那样,IEEE-754要求,当且仅当非NaN,非零浮点具有相同的位模式时,它们才能相等。因此,如果您的编译器使用符合IEEE-754的浮点数,则应该发现分配非NaN,非零浮点数会保留位模式。
实际上,您的代码
double a = 5.4;
double b = a;
Run Code Online (Sandbox Code Playgroud)
绝不应该(a == b)返回false。但是,当您替换5.4为更复杂的表达方式时,这些细微之处就消失了。这不是本文的确切主题,但是https://randomascii.wordpress.com/2013/07/16/floating-point-determinism/提到了几种看起来无辜的代码可以产生不同结果的可能方式(这打破了“相同的到位模式”声明)。特别是,您可能正在将80位中间结果与64位舍入结果进行比较,可能会产生不等式。