C 不能打印/表示非规范化浮点数吗?

Posted

技术标签:

【中文标题】C 不能打印/表示非规范化浮点数吗?【英文标题】:Can C not print/represent denormalized floating point numbers? 【发布时间】:2021-05-28 07:31:12 【问题描述】:
 $ gcc --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 12.0.5 (clang-1205.0.22.9)
Target: x86_64-apple-darwin20.3.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
$ uname -a
Darwin MacBook-Air.local 20.3.0 Darwin Kernel Version 20.3.0: Thu Jan 21 00:07:06 PST 2021; root:xnu-7195.81.3~1/RELEASE_X86_64 x86_64

代码:

#include <stdio.h>

int main(void) 


    int i = 2;
    printf("int \"2\" as %%.128f:                  %.128f\n", i);

    printf("int \"2\" as %%.128lf:                 %.128lf\n", i);

    printf("int \"2\" as %%.128LF:                 %.128Lf\n", i);

    return 0;


编译:

$ gcc floatingpointtypes.c

执行:

$ ./a.out
int "2" as %.128f:                  0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
int "2" as %.128lf:                 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
int "2" as %.128LF:                 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

当整数 2 的二进制被解释为 IEEE-754 单精度(32 位)或双精度(64 位)浮点数格式时,它是非规格化浮点数(指数位全为 0),结果值为十进制是 2e-148。

问题:

为什么我的代码打印 0? 是因为 C 无法将非规格化浮点数解释为正确的值吗?

【问题讨论】:

您有未定义的行为,因为您将int 传递给printf,其中格式说明符需要doublelong double。这是一个可变参数函数,因此不会为您完成转换。尝试将i 转换为适当的类型,您应该会得到更合理的行为。 @NateEldredge 我知道 C 可以处理表示为浮点的值 2。我试图做的是将整数 2 解释为不同精度的不同类型的浮点值。我希望我在这里传达我想要做的事情。 也许你想做类似double d; uint64_t u = 2; memcpy(&amp;d, &amp;u, 8);的事情。 可变参数函数没有原型,因此无论格式字符串说什么,您的参数都会经历default argument promotions。 charshort 提升为 int%c%d 都期望 int。但是int 不会被提升为double @NateEldredge:可变参数函数有原型。根据 C 2018 6.5.2.2 6,它们以 ... 结尾。prototype 是表单中的函数声明,其参数 lit 包括类型(用于命名参数)。如果声明是void foo(int x, ...);,它是一个原型。 C标准没有说int x是原型而...不是;整个声明是一个原型。 【参考方案1】:

如果要将 int 的“位”转换为浮点类型,最简单的方法是使用联合:

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

union u32 
    int32_t     i;
    float       f;
;

union u64 
    int64_t     i;
    double      d;
;

int main() 
    union u32 a;
    union u64 b;

    a.i = 2;
    b.i = 2;

    printf("%g\n", a.f);
    printf("%g\n", b.d);
    return 0;

结果输出:

2.8026e-45
9.88131e-324

您的代码可能发生的情况是,您使用的系统会根据类型在不同的寄存器中传递参数(整数寄存器用于 int 值,浮点寄存器用于 fp 类型)。所以调用将 2 放入一个整数寄存器,但 %f 打印的值是用于参数的第一个浮点寄存器中的任何值——可能是 0,因为在调用之前没有运行任何 fp 代码。

【讨论】:

【参考方案2】:

除了未定义的行为之外,您还高估了浮点数的大小,因为 %f 说明符需要双精度浮点数。打印的数字的确切值是2^-1073。第一个非零发生在小数点后 324 位小数位,但您只打印前 128 位数字。试试:

#include <stdio.h>

int main(void)

    long long i = 2;
    printf("long long \"2\" as %%.1073f:                  %.1073f\n", *(double *) &i);
    return 0;


输出:

long long "2" as %.1073:                  0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000988131291682493088353137585736442744730119605228649528851171365001351014540417503730599672723271984759593129390891435461853313420711879592797549592021563756252601426380622809055691634335697964207377437272113997461446100012774818307129968774624946794546339230280063430770796148252477131182342053317113373536374079120621249863890543182984910658610913088802254960259419999083863978818160833126649049514295738029453560318710477223100269607052986944038758053621421498340666445368950667144166486387218476578691673612021202301233961950615668455463665849580996504946155275185449574931216955640746893939906729403594535543517025132110239826300978220290207572547633450191167477946719798732961988232841140527418055848553508913045817507736501283943653106689453125

【讨论】:

你说得很好,只是号码不是2^-1023 + 2^-1074。非正规数没有隐含的前导 1。您也可以将i 定义为long long 并将%%.128f 修改为%%.1073f :) 哦,有趣,我不知道非正规数没有隐含的 1:3。 这也是未定义的行为。您访问了 int 占用的内存。记忆中的内容是不可预测的。这个例子没有意义。 @0___________:i 的类型为 long,在许多平台上是 64 位的。然而long long 会是一个更安全的选择。 @chqrlie 我开始写这篇评论时是int。他应该添加一个编辑而不是编辑使 cmets 无效的问题。

以上是关于C 不能打印/表示非规范化浮点数吗?的主要内容,如果未能解决你的问题,请参考以下文章

计算机组成原理——浮点数表示方法

Go语言核心技术(卷一)之2.2-浮点数

浮点数的十六进制表示

IEEE754表示浮点数

计算机中的定点数与浮点数 浮点数用 正负位 幕数 数字部分来表示

IEEE 浮点数到精确的 base10 字符串