IEEE754单精度浮点数的字节表示形式不一致

Inconsistency between byte representations of IEEE754 single-precision floats

提问人:user3414663 提问时间:8/3/2023 更新时间:8/4/2023 访问量:92

问:

当使用 Common Lisp 读取包含单精度浮点数的二进制编码的数据文件时,会出现此问题,这些编码可能是从 C/C++ 编写的。在Lisp中,我使用了ieee-floats包。然而,这导致浮点数的有效数与读取相同浮点数的 C 程序读取的浮点数略有不同。decode-float32

从表面上看,代码似乎忠实于维基百科页面上IEEE754单个浮点数的描述。此外,这个在线 IEEE-754 浮点转换器也符合该行为。ieee-floats:decode-float32

所以两个问题。为什么C版本不一致?你怎么能在Lisp中复制C的行为呢?

这一切都是在 x86_64 GNU/Linux 上使用最新版本的 sbcl、gcc 和 clang 进行的。

下面是我用来试验和确认行为的一些代码。

这里有一些 C 代码来生成一些随机浮点数及其表示为字节

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int COUNT = 16;

void main () {
  float f;
  uint32_t i;
  char* pf = (char *)&f;
  char* pi = (char *)&i;

  for(int j=0; j<COUNT; j++) {
    f = (float)rand()/(float)(RAND_MAX/2.0) - 1.0;
    pf = (char *)&f;
    pi = (char *)&i;
    memcpy(pi, pf, sizeof(float));
    printf("%9f #x%x\n", f, i);
  }
}

这里有一些 Lisp 代码来读取这些浮点数和字节,并将 Lisp 编码与 C 编码进行比较。

(ql:quickload '(:iterate :ieee-floats))

(defun scan (&optional (file "floats_and_bytes.dat"))
  (with-open-file (in file)
    (iter (for f next (read in nil))
          (for b next (read in nil))
          (while (and f b))
          (format t "~9f ~x ~x ~b~%"
                  f
                  (ieee-floats:encode-float32 f)
                  b
                  (- (ieee-floats:encode-float32 f) b)))))
C 浮点通用 LISP IEEE-754

评论

4赞 Eric Postpischil 8/3/2023
编辑问题以提供最小的可重现示例。C 函数未标准化,无法在所有 C 实现上提供相同的序列。确定行为不同的单个示例,提供一个 C 程序和一个 Lisp 程序,用该示例重现该行为,并显示每个程序的输出。rand
2赞 chux - Reinstate Monica 8/3/2023
@user3414663,尝试使用,看看问题是否仍然存在。printf("%.9g #x%x\n", f, i);
0赞 John Bollinger 8/3/2023
如果存在任何实际差异,那么这不是 C 语言本身或 Lisp 本身的问题。这将是所涉及的程序或正在使用的特定语言实现的问题。
1赞 user3414663 8/3/2023
@chux-恢复莫妮卡 您的建议解决了问题。这是打印格式问题。C(或者更具体地说,正如 @john-Bollinger 指出的 C 实现)将这些随机数只打印到小数点后 7 位,无论您使用 还是%f%9f%99f
0赞 ad absurdum 8/3/2023
“whether you use %9f or %99f -- 它们指定字段宽度,但不指定精度。的默认精度为小数点后 6 位。这是 C 标准规定的,也就是说,它不是实现细节。如果您的实现默认打印 7 位小数,则您的实现不符合标准。若要指定精度,必须使用后跟精度,例如 .%f.%10.7f%.7f

答:

3赞 chux - Reinstate Monica 8/4/2023 #1

普通浮点数具有 24 位二进制有效数,最多需要 9 个有效十进制数字来唯一打印值(以及符号和可能的指数)。float

printf("%9f #x%x\n", f, i);使用 9 个字符打印 (-1.0 ... + 1.0) 范围内的值:或 、 、 、 6 位数字。这充其量是 6 位有效数字。' ''-''0''.'


建议的更改(以及一些小的 C 改进):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int COUNT = 16;

int main() {
  union {
    float f;
    uint32_t i;
  } x;

  for (int j = 0; j < COUNT; j++) {
    x.f = (float) rand() / ((float) (RAND_MAX /2 + 1)) - 1.0f;
    printf("% .9g #x%lx\n", x.f, (unsigned long) x.i);
  }
}

示例输出

 0.380002022 #x3ec28fa0
 0.0108368397 #x3c318d00  // 10 digits after the '.', yet 9 significant digits.
 0.182981133 #x3e3b5f68
 0.109569788 #x3de06620
-0.243142366 #xbe78fa50
-0.484535754 #xbef81512
-0.585235715 #xbf15d202
 0.252523899 #x3e814ad0
-0.319745898 #xbea3b5bc
 0.687703729 #x3f300d5a
-0.862443924 #xbf5cc920
-0.180186212 #xbe3882bc
 0.75998807 #x3f428e94
-0.3610394 #xbeb8da28
 0.961136341 #x3f760d08
-0.829990387 #xbf547a40