IEEE-754 表示是否在 C 中使用?

问题描述

我必须使用 IEEE-754 对电子电荷进行编码,即 -1.602*10-19 C。我手动完成并使用 this 网站验证了我的结果。所以我知道我的代表是好的。我的问题是,如果我尝试构建一个以科学计数法显示我的数字的 C 程序,我会得到错误的数字。

这是我的代码

#include <stdio.h>
int main(int argc,char const *argv[])
{
    float q = 0xa03d217b;
    printf("q = %e",q);
    return 0;
}

结果如下:

$ ./test.exe
q = 2.688361e+09

我的问题:除了 IEEE-754 之外,还有其他表示我的 cpu 可能在内部使用浮点数吗?

解决方法

float q = 0xa03d217b;整数(十六进制)文字转换为表示该数字(或其近似值)的float值;因此,正如您所指出的,分配给您的 q 的值将是(十进制)值 2,688,360,827(这就是 0xa03d217b 等同于)。

如果您必须用其内部 IEEE-754 (HEX) 表示来初始化 float 变量,那么您最好的选择是使用 type punning via union 的成员(在 C 中合法但在 C++ 中):

#include <stdio.h>

typedef union {
    float f;
    unsigned int h;
} hexfloat;

int main()
{
    hexfloat hf;
    hf.h = 0xa03d217b;
    float q = hf.f;
    printf("%lg\n",q);
    return 0;
}

还有一些使用指针转换的“快速技巧”,例如:

unsigned iee = 0xa03d217b;
float q = *(float*)(&iee);

但是,请注意,此类方法存在许多问题,例如 potential endianness conflicts 以及您正在破坏 strict aliasing requirements 的事实。

,

因此,q 不包含您期望的值。十六进制值被转换为具有相同值(近似值)的浮点数,而不是具有相同的位表示。

当用 g++ 和选项 -Wall 编译时,有一个警告:

warning: implicit conversion from 'unsigned int' to 'float' changes value from 2688360827 to 2688360704 [-Wimplicit-const-int-float-conversion]

可以测试on Compiler Explorer

gcc 显然不支持此警告。相反,您可以使用选项 -Wfloat-conversion(不属于 -Wall -Wextra):

warning: conversion from 'unsigned int' to 'float' changes value from '2688360827' to '2.6883607e+9f' [-Wfloat-conversion]

再次on Compiler Explorer

,

我的问题是,如果我尝试构建一个以科学计数法显示我的数字的 c 程序。

如果您的目标机器可能使用或不使用 IEEE754 编码怎么办?复制位模式可能会失败。

如果从 binary32 常量 0xa03d217b 开始,代码可以检查它,然后构建可用于该实现的最佳 float

#include <math.h>
#define BINARY32_MASK_SIGN 0x80000000
#define BINARY32_MASK_EXPO 0x7FE00000
#define BINARY32_MASK_SNCD 0x007FFFFF
#define BINARY32_IMPLIED_BIT 0x800000
#define BINARY32_SHIFT_EXPO 23

float binary32_to_float(uint32_t x) {
  // Break up into 3 parts
  bool sign = x & BINARY32_MASK_SIGN;
  int biased_expo = (x & BINARY32_MASK_EXPO) >> BINARY32_SHIFT_EXPO;
  int32_t significand = x & BINARY32_MASK_SNCD;

  float y;
  if (biased_expo == 0xFF) {
    y = significand ? NAN : INFINITY;  // For simplicity,NaN payload not copied
  } else {
    int expo;
    if (biased_expo > 0) {
      significand |= BINARY32_IMPLIED_BIT;
      expo = biased_expo - 127;
    } else {
      expo = 126;
    }
    y = ldexpf((float)significand,expo - BINARY32_SHIFT_EXPO);
  }
  if (sign) {
    y = -y;
  }
  return y;
}

示例使用和输出

#include <float.h>
#include <stdio.h>
int main() {
  float e = -1.602e-19;
  printf("%.*e\n",FLT_DECIMAL_DIG,e);
  uint32_t e_as_binary32 = 0xa03d217b;
  printf("%.*e\n",binary32_to_float(e_as_binary32));
}

-1.602000046e-19
-1.602000046e-19
,

请注意,C 支持十六进制浮点数作为文字。有关详细信息,请参阅 https://en.cppreference.com/w/cpp/language/floating_literal。这种表示法对于以可移植的方式书写数字很有用,而无需担心舍入问题,就像您以常规十进制/科学表示法书写一样。这是您感兴趣的号码:

#include <stdio.h>

int main(void) {
   float f = -0x1.7a42f6p-63;

   printf("%e\n",f);
   return 0;
};

当我运行这个程序时,我得到:

$ make a
cc     a.c   -o a
$ ./a
-1.602000e-19

只要您的编译器支持这种表示法,您就不必担心底层机器如何表示浮点数,只要这个特定数字适合其 float 表示。