【问题标题】:Is it safe to assume floating point is represented using IEEE754 floats in C?假设浮点在 C 中使用 IEEE754 浮点数表示是否安全?
【发布时间】:2015-08-12 13:44:15
【问题描述】:

浮点是在 C 中定义的实现。因此没有任何保证。

我们的代码需要可移植,我们正在讨论是否可以在我们的协议中使用 IEEE754 浮点数。出于性能原因,如果我们在发送或接收数据时不必在定点格式之间来回转换,那就太好了。

虽然我知道平台和架构之间在longwchar_t 的大小方面可能存在差异。但我似乎找不到任何关于floatdouble 的具体信息。

到目前为止,我发现字节顺序可能在大端平台上颠倒了。虽然有些平台不支持浮点,但包含 floatdouble 的代码甚至无法链接。否则平台似乎坚持使用 IEEE754 单精度和双精度。

那么假设浮点在 IEEE754 中可用时是否安全?

编辑:回应评论:

你对“安全”的定义是什么?

我的意思是安全的,一个系统上的位模式在另一个系统上意味着相同(在字节旋转以处理字节顺序之后)。

【问题讨论】:

  • 对于大多数现代架构,您可以假设。但并非所有架构都使用 IEEE-754 Do any real-world CPUs not use IEEE 754?Exotic architectures the standards committees care about
  • 如果您关心生成可在通用平台上运行的代码,则此假设是安全的。如果您关心完全符合编程语言标准,那是完全不安全的。
  • 您对“安全”的定义是什么?对于生命攸关的假设,该假设是不安全的:各种实现使用 binary32 尚未完全符合 NAN、次规范等。FP 数学运算因正确性而异。最后,你需要提出一个“安全”的标准,否则答案和问题一样模糊。
  • @chux 安全的意思是,一个系统上的位模式在另一个系统上的含义相同(在字节旋转以处理字节顺序之后)。
  • @Calmarius - 便携是什么意思?这段代码是否必须在 Apple II、Cray 1、...上运行?

标签: c floating-point ieee-754


【解决方案1】:

目前非打孔卡使用中的所有架构,包括嵌入式架构和奇异的信号处理架构,基本上都提供两种浮点系统之一:

  • IEEE-754。
  • IEEE-754 除了废话。也就是说,他们大部分实现了 754,但在一些更昂贵和/或繁琐的位上却很便宜。

最常见的便宜货:

  • 将非规范化刷新为零。这使某些有时有用的定理无效(特别是,如果ab 在因子 2 内,则可以精确表示a-b 的定理),但实际上它通常不会成为问题。
  • 无法将infNaN 识别为特殊。这些架构将无法遵循将infNaN 作为操作数的规则,并且可能不会饱和到inf,而是产生大于FLT_MAX 的数字,这通常会被其他架构识别为@987654332 @。
  • 除法和平方根的正确舍入。保证结果在精确结果的 1-3 ulp 范围内比在 1/2 ulp 范围内要容易得多。一个特别常见的情况是将除法实现为倒数+乘法,这会损失一点精度。
  • 更少或没有保护数字。这是一个不寻常的便宜货,但意味着其他操作可能会降低 1-2 ulps。

BUUUUT...即使是那些除了blah架构仍然使用IEEE-754的数字表示。除了字节排序问题之外,架构 A 上描述 floatdouble 的位基本上保证在架构 B 上具有相同的含义。

因此,只要您只关心价值观的表现,您就完全可以。如果您关心操作的跨平台一致性,您可能需要做一些额外的工作。

编辑:正如 Chux 在 cmets 中提到的,平台之间不一致的一个常见额外来源是使用扩展精度,例如 x87 的 80 位内部表示。这与便宜货相反,并且(通过适当的处理)完全符合 IEEE-754 和 C 标准,但它同样会导致结果在架构之间,甚至在编译器版本之间有所不同,并且遵循明显次要和不相关的代码变化。但是:由于精度更高,特定的 x86/x64 可执行文件不会在不同的处理器上产生不同的结果。

【讨论】:

  • 您确定IBM System Z - IBM/370 的间接继承者 - 正在使用 IEEE754?
  • @BasileStarynkevitch 好记。 :) IBM 确实将他们疯狂的 base-16 系统带到了今天,但只是作为向后兼容的选项。除非您特意使用它,否则您将使用普通的 base-2 浮点数。
  • 嵌入式示例“便宜”:这些位模式不是 inf/NaN,而是简单地表示具有“IEEE754_max_exponent+1”指数的数字。另请参阅non IEEE-754 示例。
  • @Sneftel 这个答案是非常好的答案的基础。其他一些想法,根据需要使用:第一个 2 点解决了 FP 格式(OP 的问题),后面的点带来了很多计算问题。另一个“便宜”,虽然非常小众,是+/- 0.0:一些平台忽略这个标志,从不生成-0.0。 NaN(信号和安静)的类型通常适合“除了废话”类别。 NaN 有效载荷的含义和值是另一个未定义的区域。 MS 使用 64 位 long double 与 MS 历史上的 80 位使用和其他人的 128 位使用是另一个不兼容的领域。
  • @ShafikYaghmour 好吧,Higham 的“数值算法的准确性和稳定性”讨论了保护数字和非正规数的含义。 (而且,是的,还有十六进制 FP。)老实说,这是一本非常有趣的书。
【解决方案2】:

有一个宏要检查(C99 起):

C11 §6.10.8.3 条件特征宏

__STDC_IEC_559__ 整数常量 1,用于表示符合附件 F(IEC 60559 浮点运算)中的规范。

IEC 60559ISO/IEC/IEEE 60559 的缩写)是 IEEE-754 的另一个名称。

然后附录 F 建立了 C 浮点类型和 IEEE-754 类型之间的映射:

C 浮点类型与 IEC 60559 格式匹配如下:

  • float 类型符合 IEC 60559 单一格式。
  • double 类型符合 IEC 60559 double 格式。
  • long double 类型匹配 IEC 60559 扩展格式,357) else a 非 IEC 60559 扩展格式,否则为 IEC 60559 双重格式。

【讨论】:

  • 那个宏可能不是always be useful
  • 该宏未在 MSVC9 编译器中定义。
  • @Calmarius 可能是因为它不支持 C99。
  • 那个宏并不总是给你正确的答案。
  • 宏是否给出正确答案是题外话。 C 提供了一个旨在测试此功能的宏意味着该标准假定假设特定答案没有测试是不安全的。
【解决方案3】:

我建议您需要更仔细地查看您对便携的定义。

我还建议您对“安全”的定义不够充分。即使二进制表示(允许字节顺序)是好的,对变量的操作也可能表现不同。毕竟,很少有浮点的应用不涉及对变量的操作。

如果您想支持所有已创建的主机架构,那么假设 IEEE 浮点格式本质上是不安全的。您将不得不处理支持不同格式的系统、根本不支持浮点的系统、编译器具有选择浮点行为的开关(某些行为与非 IEEE 格式相关联)的系统、具有一个可选的协处理器(因此浮点支持取决于是否安装了额外的芯片,但 CPU 的其他变体是相同的),在软件中模拟浮点操作的系统(一些这样的软件模拟器可以在运行时配置),以及浮点实现有缺陷或不完整的系统(可能基于也可能不基于 IEEE)。

如果您愿意将自己限制在 2000 年后的硬件,那么您的风险较低但非零。几乎所有那个年代的 CPU 都以某种形式支持 IEEE。但是,您仍然(与较旧的 CPU 一样)需要考虑您希望支持哪些浮点运算,以及您愿意接受它们的权衡取舍。不同的 CPU(或软件仿真)对浮点的实现不如其他 CPU 完整,有些默认配置为不支持某些功能 - 因此有必要更改设置以启用某些功能,这可能会影响您的性能或正确性代码。

如果您需要在应用程序之间共享浮点值(可能位于具有不同功能的不同主机上,使用不同的编译器构建等),那么您需要定义一个协议。该协议可能涉及 IEEE 格式,但您的所有应用程序都需要能够处理协议与其本机表示之间的转换。

【讨论】:

    【解决方案4】:

    现在几乎所有常见的架构都使用 IEEE-754,这不是标准要求的。曾经有旧的非 IEE-754 架构,有些可能仍然存在。

    如果唯一的要求是交换网络数据,我的建议是:

    • 如果定义了__STDC_IEC_559__,则只对字节使用网络顺序,并假设您确实有标准的 IEE-754 浮点和双精度。
    • 如果未定义 __STDC_IEC_559__,请使用特殊的交换格式,可能是 IEE-754 - 一个单一协议 - 或其他任何协议 - 需要协议指示。

    【讨论】:

      【解决方案5】:

      就像其他人提到的那样,有 __STDC_IEC_559__ 宏,但它不是很有用,因为它仅由完全实现 C 标准中相应附件的编译器设置。有些编译器只实现了一个子集,但仍然(大部分)具有可用的 IEEE 浮点支持。

      如果您只关心二进制表示,您应该编写一个功能测试来检查某些浮点数的位模式。比如:

      #include <stdint.h>
      #include <stdio.h>
      
      typedef union {
          double   d;
          uint64_t i;
      } double_bits;
      
      int main() {
          double_bits b;
          b.d = 2.5;
          if (b.i != UINT64_C(0x4004000000000000)) {
              fprintf(stderr, "Not an IEEE-754 double\n");
              return 1;
          }
          return 0;
      }
      

      检查几个具有不同指数、尾数和符号的数字,确保安全。由于这些测试并不昂贵,您甚至可以在运行时运行一次。

      【讨论】:

      • 这里的危险来自字节排序问题。 754 没有指定浮点数是大端(符号和一些指数位于最低内存地址)还是小端(尾数的 LSB 位于最低内存地址)。 IME,FP 排序匹配整数排序,但如果你走这条路,你需要确保你做网络字节顺序转换进/出。
      • @Sneftel 当然你必须关心字节顺序。提出问题的用户提到他知道这一点。我发布的测试不管字节顺序如何都有效,并隐式验证浮点数和整数使用相同的字节顺序。
      • @Sneftel 浮点数和整数具有相同的字节序,不是吗?所以我认为上面的测试可以在两个平台上通过。
      • @Calmarius 不能保证,但我从未见过。但是,风险不在于测试会虚假失败,而是它会通过,但是将浮点数写入网络套接字并在不同的操作系统上读取它会产生不同的值。不过,正如 nwellnhof 指出的那样,OP 意识到字节顺序问题。
      【解决方案6】:

      严格来说,假设支持浮点是不安全的;一般来说,绝大多数平台都会支持它。值得注意的例外包括(现已弃用)在 Alpha 芯片上运行的 VMS 系统

      如果您有足够的运行时检查,请考虑paranoia,这是William Kahan 编写的浮点检查工具。

      编辑:听起来您的应用程序更关心二进制格式,因为它们与存储和/或序列化有关。我建议缩小范围以选择支持此功能的第三方库。你可以做得比Google Protocol Buffers 更糟。

      【讨论】:

        猜你喜欢
        • 2018-11-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-16
        • 1970-01-01
        • 2013-01-03
        • 1970-01-01
        相关资源
        最近更新 更多