为什么 long double 的精度不比 double 高?

问题描述 投票:0回答:1

我想在不损失精度的情况下检查 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
c
1个回答
2
投票

这是一个长期存在的 WSL1 错误 - https://github.com/microsoft/WSL/issues/830

您可以尝试通过添加来解决它:

#include <fpu_control.h>
...
int main() {
    unsigned short cw = 0x37f;
    _FPU_SETCW(cw);

你的程序。

© www.soinside.com 2019 - 2024. All rights reserved.