You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何long double占用更多内存却未比double提供更高精度?

关于long double在WSL1下精度不符合预期的问题

我编写了一个测试程序,用来验证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)测试后,以下结果符合预期:

  • 当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)不符——明明占用了更多内存,却没提供更高精度。

补充测试信息

测试环境是Windows 10 Pro下的WSL1 Ubuntu 16.04,编译命令为gcc-9 test_precision0100.c -o test_precision0100_longdouble.exe -DFLOATTYPE="long double",用gcc-9和内置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

对应的汇编代码:

.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的x87浮点模拟限制:
WSL1是在Windows内核上模拟Linux系统调用,它对x86的x87浮点单元(FPU)的模拟存在局限。Linux下long double对应的80位扩展精度浮点数,在WSL1中会被降级处理——内存中虽然存储的是16字节格式,但实际计算时会被截断为64位double的精度执行。

从汇编代码能看到程序使用了fldt、faddp等x87指令,但WSL1的模拟层没有真正实现80位完整精度计算,导致实际运行精度和double一致,循环提前在i=53退出。而WSL2是基于轻量级虚拟机的原生实现,能完整支持x87的80位浮点运算,所以long double可以正常发挥64位尾数的精度。

解决方式

  1. 升级到WSL2:这是最彻底的方案,WSL2完全原生支持Linux的浮点运算特性,不会有精度降级问题。
  2. 编译时指定SSE指令集:尝试用-mfpmath=sse编译选项,让GCC使用SSE指令而非x87指令处理浮点运算。不过SSE默认不支持80位long double,此时GCC会将long double映射为double,至少能让行为符合预期。

内容的提问来源于stack exchange,提问作者Ed Wynn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 22:07:22