我需要在使用 Arduino 框架使用 Platformio 为 Atmega328 构建的 C++ 项目中找出
float
和 double
的准确二进制表示。我无法访问实际硬件,因此无法亲自检查。
微控制器没有 FPU 并且是 8 位的,因此它几乎完全取决于编译器(或框架的库?) - 在本例中似乎是
avr-gcc
,版本 7.3。我已经设法了解 avr-gcc
文档 告诉我,默认情况下 double
的表示方式与 float
相同,但没有指定实际是什么(仅提到 IEEE 标准)可选 long double
)。
所以,这个问题实际上是双重的。最重要的是,我需要知道在这种特殊情况下浮点数的表示形式是什么(我强烈怀疑它是 IEEE 754,但可以使用确认)。其次,我想知道在哪里可以正式找到这些信息,作为某种官方文档的一部分。
无论如何,浮点格式是:
IEEE-754,二进制,小端。 另请参阅 avr-gcc Wiki:类型布局。
在编码形式中,表示的各个部分将占据:
32 位浮点 | 64 位浮点 | |
---|---|---|
签名 | 1 位(31) | 1 位 (63) |
有偏指数 | 8 位 (30−23) | 11 位 (62−52) |
编码尾数 | 23 位 (22−0) | 52 位 (51−0) |
指数偏差 | 127 | 1023 |
尺寸 | 4 | 8 |
NaN 是无信号的。
一些属性可用作 GCC 内置宏,例如
float
、run
> echo "" | avr-gcc -xc - -E -dM | grep _FL | sort
#define __FLOAT_WORD_ORDER__ __ORDER_LITTLE_ENDIAN__
...
#define __FLT_HAS_DENORM__ 1
#define __FLT_HAS_INFINITY__ 1
#define __FLT_HAS_QUIET_NAN__ 1
#define __FLT_MANT_DIG__ 24
#define __FLT_MAX_EXP__ 128
...
#define __FLT_MIN_EXP__ (-125)
#define __FLT_RADIX__ 2
#define __SIZEOF_FLOAT__ 4
对于
double
属性,grep 查找 __DBL
或 DOUBLE
。
直到(包括avr-gcc v9),我们有
float
= double
= long double
并且全部都是 32 位宽。
对于 avr-gcc v10 及以上版本:
double
的大小取决于命令行选项 -mdouble=[32|64]
,参见。 avr-gcc 命令行选项。此选项的默认值和可用性取决于配置选项 --with-double=...
,参见。 AVR 后端的 GCC 配置选项。 long double
和 -mlong-double=
。 --with-long-double=
。
浮点库不支持精简的微型内核 (
-mmcu=avrtiny
)。
MUL
指令的设备,
64 位浮点支持不完整。libc 完成。在 avr-gcc 中,我在文档中也看不到任何有关 float
格式的信息。要真正确认它,您确实需要硬件或阅读 avr-gcc 源代码但是您可以快速检查浮点常量,看看它们是否与 IEEE-754 值匹配。如果是,那么格式很可能是 IEEE-754。您还可以检查
__STDC_IEC_559__
是否完全符合 IEEE-754 要求
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(void) {
#ifdef __STDC_IEC_559__
puts("\n__STDC_IEC_559__ macro defined\n");
#else
puts("\n__STDC_IEC_559__ macro not defined\n");
#endif
char flt_dig = FLT_DIG; // should be 6
char flt_mant_dig = FLT_MANT_DIG; // should be 24
char flt_max_10_exp = FLT_MAX_10_EXP; // should be 38
int flt_max_exp = FLT_MAX_EXP; // should be 128
char flt_min_10_exp = FLT_MIN_10_EXP; // should be -37
char flt_min_exp = FLT_MIN_EXP; // should be -125
return 0;
}
在Godbolt中编译上述代码,并检查相应的装配线,其颜色与原始源代码相同。很容易看出这些值与预期的 IEEE-754 二进制 32 相同。但它并不完全符合 IEEE 754 标准
test.c):
float test_u_float = 0.1234f, test_s_float = -0.1234f;
double test_u_double = 0.1234, test_s_double = -0.1234;
这是为Atmega640编译的:
avr-gcc -x c -mmcu=atmega640 -O0 -save-temps test.c
并产生了这个输出(test.s):
.file "test.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
.text
.global test_u_float
.data
.type test_u_float, @object
.size test_u_float, 4
test_u_float:
.byte 36
.byte -71
.byte -4
.byte 61
.global test_s_float
.type test_s_float, @object
.size test_s_float, 4
test_s_float:
.byte 36
.byte -71
.byte -4
.byte -67
.global test_u_double
.type test_u_double, @object
.size test_u_double, 4
test_u_double:
.byte 36
.byte -71
.byte -4
.byte 61
.global test_s_double
.type test_s_double, @object
.size test_s_double, 4
test_s_double:
.byte 36
.byte -71
.byte -4
.byte -67
.ident "GCC: (GNU) 11.2.0"
.global __do_copy_data
还为amd64平台编写并编译了一个简单的程序:
#include <stdio.h>
float foo = 0.1234f, bar = -0.1234f;
int main(void)
{
printf("U %08X S %08X\n", *(unsigned*)&foo, *(unsigned*)&bar);
return 0;
}
产生了这个:
U 3DFCB924 S BDFCB924
将 test.s 中的有符号字节转换为二进制表明 GCC 在 AVR 上以与 AMD64 相同的格式对浮点数和双精度数进行编码,即 IEEE 754。