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
,其中格式说明符需要double
或long double
。这是一个可变参数函数,因此不会为您完成转换。尝试将i
转换为适当的类型,您应该会得到更合理的行为。
@NateEldredge 我知道 C 可以处理表示为浮点的值 2。我试图做的是将整数 2 解释为不同精度的不同类型的浮点值。我希望我在这里传达我想要做的事情。
也许你想做类似double d; uint64_t u = 2; memcpy(&d, &u, 8);
的事情。
可变参数函数没有原型,因此无论格式字符串说什么,您的参数都会经历default argument promotions。 char
和 short
提升为 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 不能打印/表示非规范化浮点数吗?的主要内容,如果未能解决你的问题,请参考以下文章