MIPS:除法算法(除以IEEE-754格式的有效位数)对后4-5位(LSB)给出错误的答案

时间:2019-06-26 16:20:42

标签: floating-point mips division ieee-754 qtspim

对于divisor> dividend,对于最后4-5个最低有效位,计算出的尾数会给出错误的结果。

我试图将两个IEEE-754浮点数的有效位数/尾数相除。我已经使用了这种除法算法

enter image description here

除数>股息时,尾数已标准化,但对于最后4-5位仍然不正确。

DivisionAlgorithm:#除数尾数($ a0)由除数尾数($ a1)进行25次迭代#         #返回$ v0中的商值#

    addi $sp, $sp, -12      #Decrement in $sp (Stack Pointer)
    sw $s0, 0($sp)      #Pushing $s0 into Stack
    sw $ra, 4($sp)      #Pushing $ra into Stack (Function Call Exists)
    sw $s1,8($sp)       #Pushing $s1 into stack 

    move $t0, $a0       #$t0 = Mantissa of Dividend/Remainder
    move $t1, $a1       #$t1 = Mantissa of Divisor

    add $s0, $0, $0     #$s0 = Initialization
    add $v1, $0, $0     #$v1 = 0 (Displacement of Decimal Point Initialized)
    addi $s1,$0,1       #$s1 = 1 (initialize loop variable to 1)
    add $t3,$0,33

loop:   
    beq $t1, $0, check      #If Divisor = 0, Branch to check
    sub $t0, $t0, $t1       #Dividend = Dividend - Divisor
    sll $s0, $s0, 1     #Quotient Register Shifted Left by 1-bit
    slt $t2, $t0, $0
    bne $t2, $0, else       #If Dividend < 0, Branch to else
    addi $s0, $s0, 1        #Setting Quotient LSb to 1
    j out
else:   add $t0, $t0, $t1       #Restoring Dividend Original Value

out:    srl $t1, $t1, 1     #Divisor Register Shifted Right by 1-bit
    j loop

check:  slt $t2, $a0, $a1       #If Dividend < Divisor, Call Function 'Normalization'
    beq $t2, $0, exit       #If Dividend > Divisor, Branch to exit
    move $a0, $s0       #$a0 = Quotient

    jal Normalization       #Function Call 
    j return

exit:   move $v0, $s0       #$v0 = Calculated Mantissa

return: lw $ra, 4($sp)      #Restoring $ra
    lw $s0, 0($sp)      #Restoring $s0
    lw $s1, 8($sp)      #restoring $s1
    addi $sp, $sp, 8        #Increment in $sp (Stack Pointer)
    jr $ra          #Return

规范化:#规范尾数(在$ a0中)并计算小数点移动的小数位#         #返回:         #i)$ v0 =标准化尾数         #ii)$ v1 =小数位数#

    lui $t0, 0x40       #$t0 = 0x40 (1 at 23rd-bit)
    addi $t2, $0, 1     #$t2 = 1 (Initialization)

loop2:  and $t1, $a0, $t0       #Extracting 23rd-bit of Mantissa 
    bne $t1, $0, else2      #If 23rd-bit = 1; Branch to else2
    addi $t2, $t2, 1        #Increment in Count of Decimal Places Moved
    sll $a0, $a0, 1     #Mantissa Shifted Left (To Extract Next Bit)
    j loop2         

else2:  sll $a0, $a0, 1     #Setting 24th-bit = 1 (Implied)
    move $v0, $a0       #$v0 = Normalized Mantissa
    move $v1, $t2       #$v1 = Displacement of Decimal Point    
    jr $ra          #Return

例如,我期望2.75 / 6.355的输出是00111110110111011000111011001110,但实际输出是00111110110111011000111011010110。

2 个答案:

答案 0 :(得分:3)

您的算法不正确。

恢复分区的合适算法是

qu=0
rem=dividend
repeat N times
  rem = rem - divisor
  if rem < 0
    rem = rem + divisor
    qu = (qu<<1)
  else
    qu = (qu<<1) + 1
  end
  rem = rem << 1
end

当你在

qu=0
rem=dividend
repeat N times
  rem = rem - divisor
  if rem < 0
    rem = rem + divisor
    qu = (qu<<1)
  else
    qu = (qu<<1) + 1
  end
  divisor = divisor >> 1  // instead of left shift remainder
end

由于该算法仅依赖于divisorrem的比较,因此似乎等效于右移divisor或左移rem。却不是。
右移除数时,请放开除数的最低有效位。
这可能会影响比较并因此影响商。
如果您打印其余部分,将会看到对它的影响很大,其值与正确结果之间可能会有2倍的差异。

将余数乘以2似乎很危险,因为可能会发生溢出。但是如果我们看一下算法,就会发现它不会发生。
最初,除数和除数是某些FP的尾数,因此1≤除数,除数<2,并且对rem的保留相同,其最初是除数的副本。请注意,这意味着rem <2 * div
现在,我们进行第一步计算
⚫如果rem ⚫如果rem≥div,则我们计算rem-div并将其乘以2。
由于最初rem <2 * div,rem(= 2 *(rem− div))<2 *(2 * div− div),并且属性rem <2 * div仍然为真。

因此,在每一步中,我们始终拥有rem <2 * div的属性,并且只要可以对2 * div进行编码,就可以确保rem永远不会溢出。

就实现而言,您可以在整数寄存器的24 LSB上编码这些数字。只要精度保持不变,就足够了。
在您的实现中,您循环了32次。如果要将结果写在IEEE尾数中,它是没有用的,并且可以减少迭代次数。循环就足够了
24次(尾数大小)
+1(如果被除数<除数,则第一位为0,但第二位保证为1)

如果要对结果进行四舍五入,则必须执行另外两个步骤来获得舍入和保护位。经过这两个计算步骤,如果余数≠0,则将sticky bit设置为1,否则将其设置为0。然后使用通常的规则进行舍入。

答案 1 :(得分:1)

I。分割算法本身以您开始的形式(据我所知,汇编代码也是如此)的描述缺少主要部分。准备运行除法循环时,如果在每次迭代中将除数右移1位,则最初应尽可能将其左移,并且很有用。

让我用十进制数解释一下:假设您在6位数的机器中将10001除以73(因此,则是010001除以000073)。您应该向左移动73,直到它停止适合(因此最大移位为4,而移位的除数为730000)或其最高有效位(MSD)位置高于股息的MSD(因此,我们可以在73000处停止)。然后,使用以下命令运行循环

  • shifted_divisor = 73000,shift = 3
  • shifted_divisor = 7300,shift = 2
  • shifted_divisor = 730,shift = 1
  • shifted_divisor = 73,shift = 0

(对于十进制,每个移位都应嵌套嵌套减法;对于二进制,则不需要。)

如果再向右移动73,您将失去除数的有效位数,因此最终商将大于正数。

您可以无条件地将除数左移最大宽度(730000,shift = 4),但这会浪费CPU周期。如果CPU具有CountLeadingZeros操作,则可以节省时间。

II。第二个主要时刻是您要对浮点数得出的尾数进行除法。这意味着,在最典型的两个归一化数字情况下,被除数和除数的MSB相同,并且您只会得到2个商的变式-0和1。(对于IEEE二进制32,这是将第23位设置为并且除数和除数都在8388608..16777215。)

要获得更多实数,您必须执行长除法。 (有关更经济的方法,请参见下文;现在,与教科书进行比较。)对于IEEE binary32,这意味着在最后舍入之前,至少要获得27个商位(结果尾数+ 24 +舍入+粘滞)。如上所述,在28个周期内将红利左移27位,然后将51位红利除以24位除数。

@AlainMerigot所描述的版本与我所描述的基本相同-您仍然可以计算(dividend<<quotient_width)/divisor,但是用不太明显的方式。如果将剩余数左移1,这很可能与除数右移1相同,前提是没有数据丢失。因此,第一次迭代(数字0)比较相等的位置并提供结果的MSB,但此刻它位于位0;然后,您可以在每次迭代中“激活”余数。这样可以避免双字操作。在这种算法中,如果需要最终的余数,则应该通过迭代次数右移来恢复它。但是浮动部门本身并不需要。

但是:为了正常工作,您应该从一开始就正确分配股息和除数。在十进制示例中,这意味着您不应以dividant = 10001,divisor = 73开头,而应以p股息= 10001,divisor = 73000开始。如果您的源尾数在获取整数值之前已标准化,则将自动满足此要求。否则,需要付出额外的努力,并且这些努力实际上与我为教科书划分的除数移位所描述的相同。 (这就是为什么数十年来,英特尔CPU一直遭受无限的异常处理时间的原因;)