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

MIPS汇编中double类型精度维护及小数位数修正方法

MIPS汇编浮点数读取与精度问题解决

我用MARS编写MIPS汇编程序,从txt文件读取数字存入向量,部分数字是double类型。程序逻辑整体能跑,但部分数字计算出错,比如文件里的0.672存成了0.6719999999999999,加法操作偶尔也错。试过切换单精度,问题依旧,求解决办法。

代码(葡萄牙语注释已翻译为中文)

.data

fim: .double -1.0
zero: .double 0.0
dez: .double 10.0

filename: .asciiz "xtrain.txt"
quebra_linha: .asciiz "\n"
espaco: .asciiz " "

buffer: .space 17408

.align 3
vetor: .space 4000

.text
ler_arq:    # 读取文件
    la $a0, filename
    li $a1, 0
    li $a2, 0
    
    li $v0, 13
    syscall

load:       # 将文件加载到缓冲区
    move $a0, $v0
    la $a1, buffer
    li $a2, 17408
    
    li $v0, 14
    syscall

ler_nums:   # 逐字节读取文件
    la $s0, vetor
    move $s1, $s0
    la $t0, buffer
    l.d $f10, dez
    
    copy_num:
        lb $t1, 0($t0)      # 将当前字符加载到t1
        
        bne $t1, '.', cont  # 如果不是小数点,跳转到后续步骤
        li $t3, 1       # 如果是小数点,将t3设为1(表示进入小数部分)
        addiu $t0, $t0, 1
        lb $t1, 0($t0)      # 移动到下一字节
        
        cont:
        beq $t1, ',', num_end
        beq $t1, '\r', num_end
        beq $t1, '\n', fim_copia    # 如果是逗号或换行,结束当前数字读取
        beq $t1, 0, end         # 如果文件结束,跳转到代码末尾
        
        subu $t1, $t1, 48       # 将ASCII转换为整数
        
        beq $t3, 1, le_double       # 如果t3为1,跳转到小数读取逻辑
        
        le_int:
            mul $t2, $t2, 10    # 将之前存储的整数乘以10
            add $t2, $t2, $t1   # 加上当前字符对应的数字
            j fim_copia     
        
        le_double:
            mtc1 $t1, $f0
            cvt.d.w $f0, $f0    # 转换为double类型
            
            addiu $t4, $t4, 1   # 10的幂次计数器
            
            # 循环将数字除以10^t4
            loop_double:
                div.d $f0, $f0, $f10
                addiu $t5, $t5, 1
                blt $t5, $t4, loop_double
            
            add.d $f2, $f2, $f0
            li $t5, 0
            
            j fim_copia
        
        num_end:
            mtc1 $t2, $f4
            cvt.d.w $f4, $f4    # 转换为double类型
            
            add.d $f4, $f4, $f2
            
            s.d $f4, 0($s1)
            
            li $t2, 0       # 重置整数累加器
            li $t3, 0       # 将t3设为0(退出小数模式)
            li $t4, 0       # 重置10的幂次计数器
            
            l.d $f0, zero      # 重置f0
            l.d $f2, zero
            
            addiu $s1, $s1, 8   # 移动向量中的位置
            
            j fim_copia
        
        fim_copia:
            addiu $t0, $t0, 1
            j copy_num

end:
    mtc1 $t2, $f0
    cvt.d.w $f0, $f0
    s.d $f0, 0($s1)     # 将最后一个数字存入向量
    addiu $s1, $s1, 8
    l.d $f0, fim        # 将-1.0存入向量末尾作为结束标记
    s.d $f0, 0($s1)
    l.d $f12, 0($s0)    # 将向量起始位置传入f12作为参数
    
    # 循环打印程序中的每一行和数字
    loop:
        addiu $t9, $t9, 1
        
        li $v0, 3
        syscall
        
        la $a0, espaco
        li $v0, 4
        syscall
        
        addi $s0, $s0, 8
        l.d $f12, 0($s0)
        
        bne $t9, 8, cont_fq
        
        la $a0, quebra_linha
        li $v0, 4
        syscall
        
        li $t9, 0
        
        cont_fq:
        c.eq.d $f12, $f0
        bc1f loop
解决办法

1. 接受浮点数的固有精度限制

像0.672这类十进制小数,无法用二进制浮点数(单/双精度)精确表示——二进制只能精准存储分母为2的幂的分数,其他小数会被近似截断,显示时就会出现类似0.6719999999999999的结果。这是正常现象,不是代码bug。

  • 如果必须精确表示,需要自行实现十进制浮点运算逻辑(MARS无原生支持);
  • 仅需显示正确的话,可自定义输出格式化,限制小数位数(比如保留3位)。

2. 修复小数累加的逻辑错误

当前代码中,小数位处理用循环多次除以10来计算权重,会累积额外误差。改用权重递减法更高效且误差更小:

  • 初始化小数权重为0.1,每处理一位小数就将权重除以10;
  • 当前数字乘以权重后直接加到小数累加器,避免多次除法操作。

修改后的le_double段示例:

le_double:
    mtc1 $t1, $f0
    cvt.d.w $f0, $f0    # 转换为double
    
    # 初始化或更新权重
    beq $t4, 0, init_weight
    div.d $f6, $f6, $f10  # 权重 = 权重 / 10
    j calc_decimal
init_weight:
    li $t6, 1
    mtc1 $t6, $f6
    cvt.d.w $f6, $f6
    div.d $f6, $f6, $f10  # 初始权重设为0.1
calc_decimal:
    mul.d $f0, $f0, $f6   # 当前数字 × 权重
    add.d $f2, $f2, $f0
    addiu $t4, $t4, 1   # 标记已处理小数位
    
    j fim_copia

3. 初始化未清零的寄存器

当前代码中,ler_nums段未初始化$t2、$f2等累加变量,第一次运行时这些变量的值是随机的,会导致第一个数字计算错误,也是加法出错的核心原因之一。在ler_nums开头添加初始化代码:

ler_nums:   # 逐字节读取文件
    la $s0, vetor
    move $s1, $s0
    la $t0, buffer
    l.d $f10, dez
    l.d $f2, zero       # 初始化小数累加器
    li $t2, 0           # 初始化整数累加器
    li $t3, 0           # 初始化小数标记
    li $t4, 0           # 初始化小数位计数器

4. 优化输出格式化

MARS的syscall 3会输出浮点数的所有有效位,放大近似误差。可以自己实现格式化函数,指定保留的小数位数:

  • 将浮点数乘以10^n(n为要保留的位数),转换为整数后四舍五入;
  • 拆分整数和小数部分分别打印,比如0.6719999999999999处理后会显示为0.672。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 15:15:54