【问题标题】:Is it guaranteed that the copy of a float variable will be bitwise equivalent to the original?是否保证浮点变量的副本将按位等同于原始变量?
【发布时间】:2019-08-05 22:04:27
【问题描述】:

我正在研究浮点确定性,并且已经研究了许多令人惊讶的不确定性潜在原因,我开始对复制浮点数感到偏执:

C++ 标准中的任何内容或一般情况下是否向我保证浮点左值在复制到另一个浮点变量或用作 const-ref 或按值参数时,将始终按位等效于原始值?

任何事情都会导致复制的浮点数按位与原始值等效,例如更改浮点环境或将其传递到不同的线程?

以下是一些示例代码,基于我在测试用例中检查浮点值等价性所使用的代码,这会失败,因为它需要 FE_TONEAREST:

#include <cfenv>
#include <cstdint>

// MSVC-specific pragmas for floating point control
#pragma float_control(precise, on)
#pragma float_control(except, on)
#pragma fenv_access(on)
#pragma fp_contract(off)

// May make a copy of the floats
bool compareFloats(float resultValue, float comparisonValue)
{
    // I was originally doing a bit-wise comparison here but I was made
    // aware in the comments that this might not actually be what I want
    // so I only check against the equality of the values here now
    // (NaN values etc. have to be handled extra)
    bool areEqual = (resultValue == comparisonValue);

    // Additional outputs if not equal
    // ...

    return areEqual;
}

int main()
{
    std::fesetround(FE_TOWARDZERO)
    float value = 1.f / 10;
    float expectedResult = 0x1.99999ap-4;

    compareFloats(value, expectedResult);
}

我是否必须担心,如果我将浮点值传递给比较函数,即使它是左值,它在另一端的结果可能会有所不同?

【问题讨论】:

  • 您对 NaN 在位稳定性方面的行为感兴趣吗?
  • 另外,如果您还没有阅读randomascii.wordpress.com/2013/07/16/floating-point-determinism,我建议您阅读。
  • randomascii 上的浮点系列和 gafferongames 文章是我的主要来源,还有一些散布在博客中的信息。关于 NaN:如果 NaN 是通过相同的机制产生的,我的按位检查应该适用于那些,不是吗?
  • 需要明确的是,这不是由 C++ 标准控制的,而是由您的编译器/平台、它对 IEEE-754 的遵守以及可用选项的确切影响。使用 /fp:fast 而不是 /fp:precise 进行编译不会使编译器违反 C++ 标准,因为指示浮点处理超出了 C++ 的范围。
  • @Ident:您对确定性的担忧似乎是确保浮点算术得到正确的结果,或者至少在规范内得到正确的结果,或者至少在通过不同方式计算时得到相同的结果。为此,您应该只关心表示的值,而不是表示它们的位,除了 NaN 中的任何有效负载数据。在某些浮点算术系统中,一个值以多种方式表示是完全正常的,例如 9•10^-1 和 90•10^-2 用于 .9。

标签: c++ floating-point precision floating-accuracy


【解决方案1】:

不,没有这样的保证。

次正规、非规格化浮点和 NaN 都是位模式可能不同的情况。

我相信带符号的负零可以在赋值时变成带符号的正零,尽管 IEEE754 不允许这样做。

【讨论】:

  • 最近有一个问题,原来问题是将 SNaN 分配给另一个浮点数将其更改为 QNaN 。不过我现在找不到(也许 OP 删除了它)。
  • 对于符合 IEEE 的浮点,必须保留和可靠地处理负零和正零。
  • @Deduplicator:是的,你在这一点上是正确的,我已经附加了。
  • @M.M 我想你的意思是stackoverflow.com/questions/27259234/…“在 ARM 下,sNaN 在操作中使用时会转换为 qNaN”
  • @bathsheba “[...] 都是位模式可能不同的情况。”
【解决方案2】:

C++ 标准本身对浮点数学几乎没有任何保证,因为它不强制要求 IEEE-754,而是将其留给实现(重点是我的):

[basic.fundamental/12]

有三种浮点类型floatdoublelong doubledouble 类型提供的精度至少与float 一样高,long double 类型提供的精度至少与double 一样高。 float 类型的值集是double 类型的值集的子集; double 类型的值集是long double 类型的值集的子集。 浮点类型的值表示是实现定义的。 [ 注意: 本文档对浮点运算的准确性没有要求;另见[support.limits]。 — 尾注 ]

您编写的 C++ 代码是对您希望抽象机器执行的操作的高级抽象描述,编译器完全掌握在编译器的手中。 “赋值”是 C++ 标准的一个方面,如上所示,C++ 标准不强制要求浮点运算的行为。要验证语句“赋值保持浮点值不变”,您的编译器必须根据 C++ 抽象机指定其浮点行为,而我还没有看到任何此类文档(尤其不适用于 MSVC)。

换句话说:如果没有确定确切的编译器、编译器版本、编译标志等,就不可能确定 C++ 程序的浮点语义是什么(尤其是对于诸如舍入、NaN 或签名为零)。大多数编译器会区分严格的 IEEE 一致性和放宽其中一些限制,但即使那样,您也不一定能保证程序在非优化和优化构建中具有相同的输出,例如,恒定折叠、中间结果的精度等开。

以防万一:对于gcc,即使使用-O0,您的程序不会在运行时而是在编译时计算1.f / 10,因此您的舍入模式设置将被忽略:https://godbolt.org/z/U8B6bc

您不应该对复制浮点数感到偏执,而应该对浮点的编译器优化感到偏执。

【讨论】:

  • 非常有帮助的答案。我目前正在尝试尽可能多地限制编译器,以使其完全按照我想要的方式编译我的程序的 FP 操作,因此还有编译指示、fenv 设置和我的测试,所以我希望我走上了一条好路。顺便说一句,以返回一致结果的方式编译您的示例,GCC 的 -frounding-math 标志似乎可以完成这项工作,也建议在更改 FP 舍入模式时使用。
猜你喜欢
  • 1970-01-01
  • 2022-06-23
  • 1970-01-01
  • 1970-01-01
  • 2020-06-19
  • 1970-01-01
  • 1970-01-01
  • 2018-01-27
  • 2015-12-22
相关资源
最近更新 更多