【问题标题】:Multiplying floats "manually" with integer operations将浮点数与整数运算“手动”相乘
【发布时间】:2015-08-31 17:21:05
【问题描述】:

我正在尝试在不使用 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)

【问题讨论】:

  • "从 edx 到 eax 逐位移位,同时增加指数"。我没有在您发布的代码中看到这一点。您应该发布包含您所询问问题的代码。 “但它似乎不起作用”。定义“它不起作用”。会发生什么?
  • 感谢您的回复。我添加了缺少的代码。会发生什么 - 结果不正确。
  • @Kate:既然你要求人们调试你的代码,除非你希望人们自己组装它并在他们自己的调试器中单步执行它,你应该列出你想要的值以及您实际获得的价值。 “结果不正确”比“不起作用”添加了几乎为零的新信息。
  • 我更多的是询问我的总体想法是否正确,因为我相信如果我从一开始就错了,就没有调试的意义。我想要的值是 27,乘以 15*1.8 的结果。我得到的值,我已经写的是 0。我想乘以 1.8 的每个数字都是 0。
  • @Kate:您应该将其编辑到顶部附近的问题中。还要用文字描述你的算法是什么。阅读可能存在错误的汇编代码并不是人们理解您当前算法的简单方法。正确使用算法并在 asm 中实现它实际上是两件完全不同的事情。例如,您可以用 C 编写正确的实现。然后您可以将编译器输出与您的手写 asm 进行比较。

标签: assembly x86 masm multiplication floating-point-precision


【解决方案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

【讨论】:

  • 你是对的。我选择了第二个选项,但它仍然给我一个错误的结果。总体思路好还是有一些错误?我自己也发现了一个错误,我忘了在最终指数上加 127。
  • @Kate: shrdrcr 快,并且还允许您一次移动多个位。您可以使用 32-lzcnt(或测试非零后的bsr)找出要移动的位数。
【解决方案2】:

您的算法听起来很合理。这个floating point converter 可能有助于快速查看任何给定数字的位模式应该是什么。

由于您的错误答案为零,您剩余的错误可能并不完全存在于您的代码中,而在于您如何将结果返回给程序的其余部分。尝试使用更大的数字,或在调试器中手动将 eax 设置为非零。

asm 样式:将位从一个 reg 转移到另一个 reg 的循环实现得很差。 (除了它是不需要的,见下文)。而不是无条件 jmp 回到顶部的测试,您应该在开始时 test&branch 以在需要时跳过循环,然后在循环底部放置另一个 test&branch 以仅重复需要在循环。

; 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:

是的,如果这样可以减少跳转,最好重复 test&branch。如果某些输入跳过循环,它也可能会提高 CPU 分支预测性能,但当它们没有时,它们具有相同的迭代次数。

test/jcc 的成本与单独的 jcc 大致相同,但占用更多空间。

您也许可以利用这一事实来保存指令 shr 根据结果设置零标志。但在这种情况下可能不会,因为您需要将该位放入eax,这将设置标志。


当您组合指数和尾数时,使用or 指令而不是add 会更有意义。它不会使代码更小或更快,但组合位域不同部分的常用方法是使用or。您不需要或不希望在位之间进位(这实际上不会发生,因为一个值在另一个可以有 1 的任何地方都是零)。

shr eax, 7    ; mantissa
shl ecx, 23   ; exponent
    ;; add eax, ecx ;result of multiplying
or  eax, ecx  ; combined result

实际上,这可能是另一种情况,您可以使用shrd 而不是两个班次和or

或者您可以在“正确”位置使用指数,并在添加指数时将低 23 位全为零。您可以添加1&lt;&lt;23,而不是inc。 (或shiftcount &lt;&lt; 23 没有循环)。您仍然需要从符号位获取尾数的符号。

xor 可能对处理符号位有用。 a ^ ba * b 具有相同的符号位。


当然,在这种情况下,您根本不应该使用循环。就像我评论迈克尔的回答一样,你应该使用 32-lzcnt 来计算有多少位,然后用一个 shrd 来计算。如果需要,您可以在 xor edx, edx 之后将源 reg 归零。 (如果您希望代码在没有lzcnt 的 CPU 上运行,则在测试非零后,bsr+1 而不是 32-lzcnt 是一种替代方法)

这对于异常结果应该仍然有效。上面的 32 是零,下面的 32 有前导零。但是,如果您的指数已经处于最小值,我想您无能为力,只能让它不正常。

xoring 用自身注册一个用于归零的规范习语。它比mov edx, 0 占用更少的指令字节,并且同样快。 (CPU 认为它不依赖于寄存器的先前值,因此它不会延迟乱序执行)。

【讨论】:

  • @Kate:用 cmets 更新了我的答案,关于最后将尾数和指数结合起来的位。
猜你喜欢
  • 2016-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-12
  • 1970-01-01
  • 2023-03-05
  • 1970-01-01
相关资源
最近更新 更多