我发现牛顿法x = (x + n/x) / 2只用整数操作时效果不佳,因为终止条件难以准确计算。 n/2 只是一个猜测,几乎总是比必要的迭代次数多。牛顿法收敛二次,与n 不成正比,而是与sqrt(n) 成正比。另一个建议,“继续重复直到 x 停止变化”也不起作用,因为对于非完美正方形 x 将在根的地板和天花板之间交替 - 因为整数数学,术语 n/x 将在x 比sqrt(n) 略小或略大。
我从wikipedia 中获取了逐位根计算方法,并创建了 MIPS 版本。它没有效率低下(n/2)或模棱两可(floor(sqrt(n)) 或ceil(sqrt(n)))。查找表方法可以更有效地返回结果,但假设查找表不可用,这是一种很好且可靠的方法。
首先,我将 C 示例翻译为仅使用小于 (<) 比较,因为 MIPS 仅提供 set-less-than slt 比较指令。
int isqrt(int num) {
int ret = 0;
int bit = 1 << 30; // The second-to-top bit is set
// "bit" starts at the highest power of four <= the argument.
while (num < bit) {
bit >>= 2;
}
while (bit != 0) {
if (num < ret + bit) {
ret >>= 1;
} else {
num -= ret + bit;
ret = (ret >> 1) + bit;
}
bit >>= 2;
}
return ret;
}
这是生成的 MIPS 代码:
isqrt:
# v0 - return / root
# t0 - bit
# t1 - num
# t2,t3 - temps
move $v0, $zero # initalize return
move $t1, $a0 # move a0 to t1
addi $t0, $zero, 1
sll $t0, $t0, 30 # shift to second-to-top bit
isqrt_bit:
slt $t2, $t1, $t0 # num < bit
beq $t2, $zero, isqrt_loop
srl $t0, $t0, 2 # bit >> 2
j isqrt_bit
isqrt_loop:
beq $t0, $zero, isqrt_return
add $t3, $v0, $t0 # t3 = return + bit
slt $t2, $t1, $t3
beq $t2, $zero, isqrt_else
srl $v0, $v0, 1 # return >> 1
j isqrt_loop_end
isqrt_else:
sub $t1, $t1, $t3 # num -= return + bit
srl $v0, $v0, 1 # return >> 1
add $v0, $v0, $t0 # return + bit
isqrt_loop_end:
srl $t0, $t0, 2 # bit >> 2
j isqrt_loop
isqrt_return:
jr $ra
您可以像任何其他 MIPS 过程一样调用它:
addi $a0, $zero, 15
jal isqrt # v0 = result
这个过程总是返回$v0 = floor(sqrt($a0))来表示肯定的参数。
当心:代码进入负参数的无限循环。在调用此过程之前清理您的输入。