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

MIPS汇编二分法求平方根程序异常问题求助

二分法求平方根的MIPS汇编循环异常问题

我在将一段二分法计算平方根的C程序转换为MIPS汇编代码时遇到了问题:程序的while循环比预期少执行一次,比如计算sqrt(16)时返回8,计算sqrt(4)时返回4。我尝试过调整栈中结果的存储位置,但没有解决问题,目前代码回到初始状态,使用QtSpim编译运行。

原C代码

#include <stdio.h>

float fsqrt(float x) {
    if (x == 0)
        return 0;

    float xhi = x;
    float xlo = 0;
    float guess = x / 2;
    float error = 0;

    error = guess * guess - x;

    if (error < 0) {
        error = 0 - error;
    }

    while (error > 0.00001) {
        if (guess * guess > x) {
            xhi = guess;
        } else {
            xlo = guess;
        }

        guess = (xhi + xlo) / 2;
        error = guess * guess - x;

        if (error < 0) {
            error = 0 - error;
        }
    }

    return guess;
}

int main() {
    float number;
    printf("Enter a non-negative number: ");
    scanf("%f", &number);
    printf("Square root is %.6f\n", fsqrt(number));
    return 0;
}

当前MIPS代码

.data
float0: .float 0.0 # For comparing floats to 0
errorCheck: .float 0.00001 # Error margin for correct sqrt
enterText: .asciiz "Enter a non-negative number: "
outputText: .asciiz "Square root is "
newLine: .asciiz "\n"

    .text
main:
    # Request number
    li $v0, 4
    la $a0, enterText
    syscall

    # Read number (float)
    li $v0, 6
    syscall
    s.s $f0, 0($sp) # Save number to 0 on stack
   
    # Calculate square root
    jal fsqrt
   
    # Print square root text
    li $v0, 4
    la $a0, outputText
    syscall
   
    # Print square root number
    li $v0, 2
    l.s $f12, 4($sp)
    syscall
   
    # Print new line
    li $v0, 4
    la $a0, newLine
    syscall
   
    # Exit program
    li $v0, 10
    syscall
   
fsqrt:
    l.s $f0, 0($sp)      # Load input number into $f0(x) from stack
    l.s $f31, float0     # Load float0 into $f31
    li.s $f29, 2.0       # Load 2.0  into $f29 for divisor
    l.s $f28, errorCheck # Load error check into $f28 for checking error
    
    # Increase stack for output/return (Return will be saved to 4($sp))
    addi $sp, $sp, -4
    
    # Return 0 if input == 0
    c.eq.s $f0, $f31
    bc1t return0
    
    mov.s $f1, $f0       # Load x into "xhi"
    mov.s $f2, $f31      # Load 0 into "xlo"
    div.s $f3, $f0, $f29 # Load x/2 into "guess"
    mov.s $f4, $f31      # Load 0 into "error"
    
    mul.s $f30, $f3, $f3 # Load guess^2 into $f30 temporarily
    sub.s $f4, $f30, $f0 # error = guess^2 - x
    
    # Error = -Error if less than 0
    c.lt.s $f4, $f31
    bc1t errorLTZero
    
    j while

while:
    # If error > 0.00001, end loop (exit function)
    c.le.s $f4, $f28
    bc1f exitFunc
    
    mul.s $f30, $f3, $f3 # Loads guess^2 into $f30 for conditional
        
    # if guess^2 < x, xlo = guess
    c.lt.s $f30, $f0
    bc1t guessSqLTx
        
    # else xhi = guess
    mov.s $f1, $f3
        
    add.s $f30, $f1, $f2  # Load "xhi + xlo" into temp float register
    div.s $f3, $f30, $f29  # Load "xhi + xlo / 2" into guess
    
    mul.s $f30, $f3, $f3 # Load guess^2 into $f30 temporarily
    sub.s $f4, $f30, $f0 # Load "guess - x" into $f30 temporarily+
    
    # Error = -Error if less than 0
    c.lt.s $f4, $f31
    bc1t errorLTZero    
        
    s.s $f3, 4($sp) # Save guess to stack
        
    j while
    
j exitFunc

return0: 
    s.s $f31, 4($sp)
    jr $ra
        
errorLTZero:
    sub.s $f4, $f31, $f4
    jr $ra
    
exitFunc:
    s.s $f3, 4($sp)
    jr $ra

guessSqLTx:
    mov.s $f2, $f3
    jr $ra

问题分析与修复方案

核心问题1:循环条件判断逻辑颠倒

在MIPS的while标签处,判断逻辑完全反转:

# If error > 0.00001, end loop (exit function)
c.le.s $f4, $f28
bc1f exitFunc

c.le.s $f4, $f28是判断error <= 0.00001,bc1f exitFunc表示条件不成立(即error > 0.00001)时退出循环,这和原C代码的while (error > 0.00001)逻辑完全相反,导致第一次循环直接退出,没有执行迭代。

修复:

while:
    # 如果 error <= 0.00001,退出循环
    c.le.s $f4, $f28
    bc1t exitFunc

使用bc1t(条件成立时跳转),当误差符合要求时才退出循环,否则继续迭代。

核心问题2:分支处理后错误返回主程序

errorLTZero和guessSqLTx分支使用jr $ra返回,而$ra是fsqrt的调用返回地址,直接跳回会中断循环。正确做法是处理完后跳回循环流程,而非返回主程序。

修复:

  1. 拆分初始误差处理和循环内误差处理的分支:
# 初始误差处理
c.lt.s $f4, $f31
bc1t errorLTZero_init

j while

errorLTZero_init:
    sub.s $f4, $f31, $f4
    j while
  1. 调整循环内分支的跳转逻辑,避免直接返回:
mul.s $f30, $f3, $f3 # Loads guess^2 into $f30 for conditional
    
# if guess^2 < x, xlo = guess
c.lt.s $f30, $f0
bc1t guessSqLTx
    
# else xhi = guess
mov.s $f1, $f3
j update_guess

guessSqLTx:
    mov.s $f2, $f3

update_guess:
    add.s $f30, $f1, $f2  # Load "xhi + xlo" into temp float register
    div.s $f3, $f30, $f29  # Load "xhi + xlo / 2" into guess
    
    mul.s $f30, $f3, $f3 # Load guess^2 into $f30 temporarily
    sub.s $f4, $f30, $f0 # Load "guess^2 - x" into error
    
    # Error = -Error if less than 0
    c.lt.s $f4, $f31
    bc1t errorLTZero_loop    
        
    s.s $f3, 4($sp) # Save guess to stack
        
    j while

errorLTZero_loop:
    sub.s $f4, $f31, $f4
    j while

其他优化点

  • 避免使用$f31:$f31是浮点返回寄存器,改用其他空闲寄存器(如$f20)存储常量,避免冲突。
  • 栈操作保持一致性:确保返回值存储位置不会覆盖输入数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 03:19:56