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

x86汇编无浮点指令实现浮点数减法结果不一致问题排查

问题背景
  • 实现目标:不使用x86浮点指令,手动完成两个IEEE754单精度浮点数的减法运算,当前阶段固定符号位为0,优先实现正浮点数减法逻辑
  • 异常现象:部分计算结果符合预期(如10.9 - 2.5可得到正确结果8.4),部分计算结果错误(如5.7 - 2.5得到错误值7.2)
  • 实现思路:先对齐两个操作数的指数,再对带隐含1的尾数执行减法,最后将符号位、指数、尾数重组为标准浮点数格式

对应实现代码如下:

; expands a floating point number into sign, exponent and fraction
%macro expand 1
    push rax
    mov eax, %1
    rol eax, 1
    mov byte [sign], al
    and byte [sign], 1
    rol eax, 8
    mov byte [exponent], al
    shr eax, 9
    or eax, 800000h
    mov dword [fraction], eax
    pop rax
%endmacro

; combines sign, exponent and fraction to make a floating point number
%macro combine 1
    push rsi
    push rcx
    push rax
    push rbx
    push rdx
    mov esi, %1
    xor ecx, ecx
    mov eax, [fraction]
    mov ebx, [exponent]
    mov edx, [sign]
    shrd ecx, eax, 23
    shrd ecx, ebx, 8
    shrd ecx, edx, 1
    mov dword [esi], ecx
    pop rdx
    pop rbx
    pop rax
    pop rcx
    pop rsi
%endmacro

section .bss 
    result resd 1

section .data
    source dd 5.7
    source2 dd 2.5

section .text
    global _start 

    _start:
        xor ebx, ebx
        expand [source]
        ; store expanded parts in these registers
        mov bl, [exponent]
        mov eax, [fraction]
        mov cl, [sign]
        expand [source2]

        ; make exponents even
        while:
            cmp [exponent], bl
            jnl endwhile
            inc byte [exponent]
            shr dword [fraction], 1
            jmp while
        endwhile:

        while2:
            cmp bl, [exponent]
            jnl endwhile2
            inc bl
            shr eax, 1
            jmp while
        endwhile2:


        ; make operand 2 negative and then add them together
        neg dword [fraction]
        add eax, dword [fraction]
        mov [fraction], eax
        mov [exponent], bl
        mov byte [sign], 0
        combine result
        mov edx, [result]

        exit
核心错误点

1. 指数对齐循环跳转目标错误

第二个指数对齐循环while2的末尾跳转指令写为jmp while,会直接跳回第一个对齐循环,导致第一个操作数(被减数)的尾数右移、指数递增逻辑完全无法按预期执行,这是5.7 - 2.5计算错误的直接原因。该位置正确跳转目标应为while2,保证循环在自身逻辑内完成两个方向的指数对齐。

2. 尾数运算后缺失规格化步骤

IEEE754单精度浮点数要求尾数必须为1.xxxxxx的形式(最高位1默认隐含,不占用存储位),当前代码在尾数完成减法运算后,没有做任何规格化处理:

  • 如果减法(补码加法)后产生进位,最高位超过第23位(即手动补的隐含1所在位),需要将尾数右移1位,对应指数加1
  • 如果减法后最高位的1不在第23位,需要循环左移尾数直到最高位1落到第23位,左移多少位就将指数对应减多少
    直接将未规格化的尾数传入重组逻辑,只要运算结果不是刚好落在标准规格化位置,就会输出错误值。

3. 浮点数重组逻辑存在位提取错误

combine宏中使用shrd指令提取尾数时,取的是尾数的高23位,而实际需要存储的是隐含1之后的23位小数部分(即补完隐含1的尾数的低23位),会将隐含的1也存入尾数字段,导致结果偏差。

关键修正点
  1. 修正while2循环的跳转目标:
while2:
            cmp bl, [exponent]
            jnl endwhile2
            inc bl
            shr eax, 1
            jmp while2  ; 原错误写为jmp while
        endwhile2:
  1. 在尾数运算完成后、存入内存前增加规格化逻辑:
neg dword [fraction]
        add eax, dword [fraction]

        ; 处理进位:尾数最高位超过23位时右规
        rshift_norm:
            test eax, 0xff000000
            jz lshift_norm_check
            shr eax, 1
            inc bl
            jmp rshift_norm
        ; 处理最高位未对齐:左规直到隐含1落到bit23
        lshift_norm_check:
            test eax, 0x800000
            jnz norm_finish
            shl eax, 1
            dec bl
            jmp lshift_norm_check
        norm_finish:

        mov [fraction], eax
        mov [exponent], bl
        mov byte [sign], 0
  1. 修正combine宏的位拼接逻辑,避免提取到隐含的高位1,直接用移位+或运算拼接更直观,不容易出错:
%macro combine 1
    push rsi
    push rcx
    push rax
    push rbx
    push rdx
    mov esi, %1
    mov eax, [fraction]
    mov ebx, [exponent]
    mov edx, [sign]
    and eax, 0x7fffff  ; 去掉bit23的隐含1,只保留23位小数
    mov ecx, edx
    shl ecx, 31  ; 符号位移到最高位
    or ecx, ebx
    shl ecx, 23  ; 指数移到bit23~bit30
    or ecx, eax  ; 拼接尾数
    mov dword [esi], ecx
    pop rdx
    pop rbx
    pop rax
    pop rcx
    pop rsi
%endmacro

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 23:33:34