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也存入尾数字段,导致结果偏差。
关键修正点
- 修正
while2循环的跳转目标:
while2: cmp bl, [exponent] jnl endwhile2 inc bl shr eax, 1 jmp while2 ; 原错误写为jmp while endwhile2:
- 在尾数运算完成后、存入内存前增加规格化逻辑:
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
- 修正
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
相关产品推荐
相关产品推荐

