我想在不损失精度的情况下检查 C 中各种浮点类型可以容纳的最大整数。这是一个测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#define FLOATTYPE long double
#define ONE ((FLOATTYPE)1.0)
#define TWO ((FLOATTYPE)2.0)
int
main(int argc,char*argv[]){
int i;
FLOATTYPE x;
x = ONE;
for(i=0;;++i){
printf("1.0<<%3d: x=%.0Lf",i,(long double)x);
if((x+ONE)!=x &&
(x+ONE)- x == ONE){
printf(" ... can increment without loss of precision\n");
}else{
printf(" ... cannot increment without loss of precision\n");
break;
}
x *= TWO;
}
printf("FLT_RADIX = %d\n",FLT_RADIX);
printf("FLT_MANT_DIG = %d\n",FLT_MANT_DIG);
printf("DBL_MANT_DIG = %d\n",DBL_MANT_DIG);
printf("LDBL_MANT_DIG = %d\n",LDBL_MANT_DIG);
printf("\nsizeof(FLOATTYPE) = %lu\n",sizeof(x));
}
一些结果(使用 gcc-9 (Ubuntu 9.4.0-1ubuntu1~16.04) 9.4.0):
当
FLOATTYPE
为float
时:sizeof
为4,循环在i==24
处退出,等于FLT_MANT_DIG
.
当
FLOATTYPE
为double
时:sizeof
为8,循环在i==53
处退出,等于DBL_MANT_DIG
.
当
FLOATTYPE
为__float128
时:sizeof
为16,循环在i==113
处退出。
他们都有道理。然而:
FLOATTYPE
为long double
时:sizeof
为16,循环在i==53
处退出,不等于LDBL_MANT_DIG
(即64)。似乎
long double
比double
占用更多的内存,但没有提高精度。怎么会?
编辑:关于编译器等的更多细节: 这是在 Windows 10 Pro 机器上,在 Linux 1 的 Window 子系统中托管 Ubuntu 16.04。编译器从
gcc-9 -v
: 报告这个
Using built-in specs.
COLLECT_GCC=gcc-9
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~16.04' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-SATzbE/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~16.04)
编译的命令很简单:我把
FLOATTYPE
的定义注释掉,这样我就可以编译不同的版本,然后运行:
gcc-9 test_precision0100.c -o test_precision0100_longdouble.exe -DFLOATTYPE="long double"
然后跑
./test_precision0100_longdouble.exe
。
除了 -Wall -Wextra -pedantic -std=c99
和 argc
的未使用参数外,编译器不会给出任何带有 argv
的警告消息。
我得到与上面提供的代码中定义的 FLOATTYPE 相同的结果。我也使用内置的 gcc v5.4.0 获得了相同的异常结果,但在另一台在 WSL2 上托管 Ubuntu 18.04 的机器上却没有。输出看起来像我的描述所期望的那样,结尾:
1.0<< 50: x=1125899906842624 ... can increment without loss of precision
1.0<< 51: x=2251799813685248 ... can increment without loss of precision
1.0<< 52: x=4503599627370496 ... can increment without loss of precision
1.0<< 53: x=9007199254740992 ... cannot increment without loss of precision
FLT_RADIX = 2
FLT_MANT_DIG = 24
DBL_MANT_DIG = 53
LDBL_MANT_DIG = 64
sizeof(FLOATTYPE) = 16
这里是来自“gcc -S test_precision0100.c”的“test_precision0100.s”,如上:
.file "test_precision0100.c"
.text
.section .rodata
.LC1:
.string "1.0<<%3d: x=%.0Lf"
.align 8
.LC2:
.string " ... can increment without loss of precision"
.align 8
.LC3:
.string " ... cannot increment without loss of precision"
.LC4:
.string "FLT_RADIX = %d\n"
.LC5:
.string "FLT_MANT_DIG = %d\n"
.LC6:
.string "DBL_MANT_DIG = %d\n"
.LC7:
.string "LDBL_MANT_DIG = %d\n"
.LC8:
.string "\nsizeof(FLOATTYPE) = %lu\n"
.text
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $48, %rsp
movl %edi, -36(%rbp)
movq %rsi, -48(%rbp)
fld1
fstpt -16(%rbp)
movl $0, -20(%rbp)
.L5:
movl -20(%rbp), %eax
pushq -8(%rbp)
pushq -16(%rbp)
movl %eax, %esi
movl $.LC1, %edi
movl $0, %eax
call printf
addq $16, %rsp
fldt -16(%rbp)
fld1
faddp %st, %st(1)
fldt -16(%rbp)
fucomip %st(1), %st
jp .L9
fldt -16(%rbp)
fucomip %st(1), %st
fstp %st(0)
je .L2
jmp .L7
.L9:
fstp %st(0)
.L7:
fldt -16(%rbp)
fld1
faddp %st, %st(1)
fldt -16(%rbp)
fsubrp %st, %st(1)
fld1
fucomip %st(1), %st
jp .L10
fld1
fucomip %st(1), %st
fstp %st(0)
jne .L2
movl $.LC2, %edi
call puts
fldt -16(%rbp)
fadd %st(0), %st
fstpt -16(%rbp)
addl $1, -20(%rbp)
jmp .L5
.L10:
fstp %st(0)
.L2:
movl $.LC3, %edi
call puts
nop
movl $2, %esi
movl $.LC4, %edi
movl $0, %eax
call printf
movl $24, %esi
movl $.LC5, %edi
movl $0, %eax
call printf
movl $53, %esi
movl $.LC6, %edi
movl $0, %eax
call printf
movl $64, %esi
movl $.LC7, %edi
movl $0, %eax
call printf
movl $16, %esi
movl $.LC8, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~16.04) 9.4.0"
.section .note.GNU-stack,"",@progbits
这是一个长期存在的 WSL1 错误 - https://github.com/microsoft/WSL/issues/830
您可以尝试通过添加来解决它:
#include <fpu_control.h>
...
int main() {
unsigned short cw = 0x37f;
_FPU_SETCW(cw);
你的程序。