我正在尝试在不使用FP硬件指令的情况下实现浮点乘法。
我认为我的代码适用于符号位和指数位,但不适用于尾数。
总体思路:
1.添加这两个数字的指数
2.将它们的尾数相乘。
3.标准化尾数。
4.将标准尾数标准化的部分加到指数中
我现在忽略了符号位,因为我在高于0的值上测试它。
这就是问题所在:我试图将这两个尾数相乘然后 - 因为结果将在两个寄存器edx中:eax - 从edx到eax逐个移位位同时增加指数。 但它似乎没有用,所以我想知道我的想法是否合适,或者有更好的方法可以做到这一点?
以下是我在MASM中所写的内容:
mov eax, [ebp+8] ;put into eax one of numbers to multiply
mov ecx, a ;in ecx is second number to multiply, constant = 1.8
and ecx, 7F800000H ;mask to get exponent
and eax, 7F800000H
shr ecx, 23
shr eax, 23
sub ecx, 127
sub eax, 127
add ecx, eax ;exponent of the final number - later should be added part got from mantissa
mov eax, [ebp+8]
mov edx, a
and eax, 007FFFFFH ;getting mantissa
and edx, 007FFFFFH
; editor's note: unsure if there were any unlisted instructions
; between the two code in the original
mul edx ; multiply the mantissas
mov ebx, 0
spr:
cmp edx, 0 ;check if edx is cleared out
jne przesun
je dalej
przesun:
inc ecx
shr eax, 1 ;making space for new bit
shr edx, 1 ;put bit to CF
bts eax, 31 ;putting bit from CF ; Bug #1, see Michael's answer
jmp spr
dalej:
shr eax, 7
shl ecx, 23
add eax, ecx ;result of multiplying
对于我尝试的每个数字乘以1.8,结果为0。
(atm我在15号测试它,所以结果应该是27)
答案 0 :(得分:1)
bts eax, 31 ;putting bit from CF
^ BTS
没有做你认为它做的事情。
引自英特尔手册(重点补充):
在由指定的位位置选择位串(由第一个操作数指定,称为位基)中的位 位偏移操作数(第二个操作数),将该位的值存储在CF标志中,并将所选位设置为 位串到1 。位基操作数可以是寄存器或存储单元;位偏移操作数可以是寄存器 或直接的价值。
所以你总是把这个位设置为1,不管你刚刚移出的位的值。
您可以使用其他说明来完成您要执行的操作:
shrd eax, edx, 1 ; Shift eax 1 bit to the right, with the new MSB shifted in from edx
shr edx,1 ; The shrd above doesn't modify edx, so discard the old LSB of edx
或:
shr edx, 1 ; CF = edx.0
rcr eax, 1 ; rotate through carry; shift in CF from the left and shift out eax.0
答案 1 :(得分:0)
你的算法听起来很合理。对于任何给定的数字,这个floating point converter可能很快就会有用。
由于您的错误答案为零,您的剩余错误可能不在您的代码中,而是在您如何将结果返回到程序的其余部分。尝试使用更大的数字,或在调试器中手动将eax
设置为非零。
asm style:你的循环将位从一个reg转移到另一个reg很难实现。 (除了它不需要的事实,见下文)。如果需要,您应该在开始时测试& branch以跳过循环而不是无条件jmp
返回测试,然后在循环的底部放置另一个测试和分支以仅重复指令需要在循环中。
; mov ebx, 0 ; was this supposed to be ecx?
; ebx doesn't show up anywhere else in your code
xor ebx, ebx
spr:
; cmp edx, 0 ;check if edx is cleared out
test edx, edx ; shorter encoding when testing for 0
jz dalej ; jz and je are the same instruction
; else fall through into the loop. Your old version used two branches here >.<
przesun:
inc ecx
shr eax, 1 ;making space for new bit
shr edx, 1 ;put bit to CF
; bts eax, 31 ;bug, but Michael's answer covered that
test edx,edx
jnz przesun
dalej:
是的,如果减少跳跃,最好重复测试和分支。如果某些输入跳过循环,它也可能会提高CPU分支预测性能,但是当它们没有时,它们具有相同的迭代次数。
test/jcc
与jcc
的费用大致相同,但占用的空间更多。
您可以通过利用这一事实来保存指令
shr
根据结果设置Zero标志。但不是在这种情况下,因为您需要将该位置于eax
,这将设置标志。
组合指数和尾数时,使用or
指令而不是add
会更有意义。它不会使代码更小或更快,但组合位域的不同部分的常用方法是使用or
。你不需要或想要在比特之间携带(这实际上不会发生,因为一个值为零,而另一个值可以为零)。
shr eax, 7 ; mantissa
shl ecx, 23 ; exponent
;; add eax, ecx ;result of multiplying
or eax, ecx ; combined result
实际上,这可能是另一种情况,您可以使用shrd
而不是两个班次和or
。
或者你可以使用指数处于“正确”位置,并在添加指数时将低23位全部置零。您需要添加inc
,而不是1<<23
。 (或shiftcount << 23
没有循环)。你仍然需要在符号位上找到尾数符号。
xor
可能对处理符号位有用。 a ^ b
与a * b
具有相同的符号位。
当然,在这种情况下,你不应该使用循环。就像我评论迈克尔的答案一样,你应该使用32 - lzcnt
计算有多少位,然后用一个shrd
来计算。如果需要,您可以使用xor edx, edx
将源注册归零。 (bsr
+ 1在测试非零之后,而不是32-lzcnt
,如果您希望代码在没有lzcnt
的情况下在CPU上运行,则可以选择{/ 1}}
这仍应适用于非正常结果。上部32为零,下部32为前导零。但是,如果你的指数已经达到最小值,我想你无能为力,只能让它不正常。
xor
一个寄存器本身就是用于归零的规范习惯用法。它比mov edx, 0
需要更少的指令字节,并且速度也一样快。 (CPU认为它不依赖于寄存器的先前值,因此它不会延迟无序执行。)