【问题标题】:Why are there different machine-instructions for signed mul / div?为什么签名的 mul / div 有不同的机器指令?
【发布时间】:2018-01-16 20:22:48
【问题描述】:

我一直认为有符号乘法和除法需要与相应的无符号运算不同的电路逻辑。在另一个论坛上有人说,unsigned-ALUs 可以在不修改有符号参数的情况下使用并给出有效的结果。

我只是用一个小程序测试了这个

#include <stdio.h>

void main()
{
    short a, b;
     long long c;

    c = 0;
    a = -32768;
    do
    {
        b = -32768;
        do
            if( (int)((unsigned)a * (unsigned)b) != ((int)a * b) )
                ++c;
        while( ++b != -32768 );
    } while( ++a != -32768 );

    printf( "%.0lf\n", (double)c );
}

这将打印零,即乘法的工作方式类似于上述假设。那么为什么有符号和无符号乘法有不同的指令,这在电路逻辑中是如何表示的呢?

而且我没有谈论部门。我是否正确地假设,我之前的假设是至少需要不同的电路逻辑来进行划分?

【问题讨论】:

  • 乘法给出与操作数相同大小的结果并不关心符号。可以为您提供完整双倍大小结果的变体。
  • 我不知道你从哪里得到 unsigned-ALU 无需修改即可用于签名操作。那是完全错误的。许多操作(如添加和非扩展 mul)不依赖于操作数的符号,但有些操作,如扩展 mul/div,尽管您可以轻松地从无符号操作中获得有符号结果,反之亦然Why are signed and unsigned multiplication different instructions on x86(-64)?,@987654322 @
  • 也就是说,您可以使用相同的逻辑和一些技巧来执行有符号和无符号操作,而无需加倍电路。还有void main() is wrong
  • 这是一个很好的问题,但请注意,使用 C 的“测试”部分充满了未定义的行为 (signed integer overflow)、乘法 (-32768 * -32768 overflows a signed int) 和循环计数器!如果使用 gcc,this code is safe with -fwrapv,我想。 (如果没有它,它可能会发生,尤其是-O0。)
  • "void main()",未定义的溢出行为 - 这一切在这里都无关紧要。

标签: assembly integer-arithmetic


【解决方案1】:

这取决于参数的大小。如果将两个 8 位值相乘,则需要 16 位来存储最坏情况的结果,对吗? 0xFF * 0xFF = 0xFE01。 255*255 = 65025。

从小学开始,我们如何用手做乘法?我有两个,两个位数

   ab
*  cd
======

让我们说 cd 两者都是为了使这一点显而易见。

    ab
 *  11
======
   0ab
+  ab 
======

如果我们认为这些是无符号数,那么 ab 和 cd 左侧的位将用零填充。但如果这些是有符号数,那么这些位是符号扩展的

  000ab
* 000cd
========

  xxxab
* yyycd
=======

如果我采用两位操作数和两位结果,那么这无关紧要

    ab
 *  11
======
    ab
+   b 
======

求和中的位无关紧要。但如果我想要一个两位操作数和一个 4 位结果,那就很重要了。

如果您想要一个 32 位分母,那么除法就是您想要从 64 位分子开始的相反,所以 32 位除以 32 位是分子有符号还是无符号?当您将其填充到 64 位以进行除法时,您是对其进行符号扩展还是对其进行零填充(有符号或无符号)。结果各不相同。

一些指令集没有二比一,操作数的大小都相同,因此您“简单地”必须综合它,但您可以根据小学知识按照自己的方式进行。

加法和减法不关心他们不知道的有符号和无符号,减法使用加法器,二进制补码的美丽,你反转并加一个所以你反转第二个操作数,然后将一个 1 输入到进位中lsbit 然后加法,把加法器变成减法器。二进制补码负责其余部分,结果无论哪种方式都有效。无符号溢出与有符号溢出确实很重要,如果两者都被计算,那么你的加法器会两者兼而有之。无符号溢出是msbit的进位,有符号是msbit的进位和进位的比较,也可以从输入和结果的msbits来确定。

【讨论】:

    【解决方案2】:

    在另一个论坛上,有人说,无符号 ALU 无需对有符号参数进行任何修改即可使用并给出有效结果。

    要理解这个矛盾,我们应该记住,一个数字(这里是十进制数)在左边和右边都有“隐式”数字:

    12345 = 00...0000000012345.000000000...00
    

    对于正数和无符号二进制数也是如此;在这种情况下,隐含数字为 0:

    01110100 = 00...0000000001110100.0000...00
    

    对于存储为二进制补码的负数,左侧的隐式数字是一,而不是零:

    10110100 = 11...1111111110110100.0000...00
    

    每当左侧的“隐式”数字之一影响运算结果时,我们就需要运算的无符号和有符号变体:

    换档

    将右边的隐式数字左移(小数点后)会影响结果;这总是 0。

    但是,将左侧第一个隐式数字右移会影响结果;确实有两个“右移”操作:“SRL”(无符号)和“SRA”(有符号)。

    加法、减法和乘法

    在这些情况下,结果也会有所不同:

    1111 + 0011 = 11111 + 00011 = 00010 (signed)
    1111 + 0011 = 01111 + 00011 = 10010 (unsigned)
    

    但是,大多数 CPU 提供的操作只会返回与输入数据一样多的位:如果您将两个 8 位数字相加,您将只得到结果的低 8 位,而不是整个 9 位。并且低 8 位在有符号和无符号运算之间没有区别。

    如果有一个 CPU 可以将两个 8 位数字相加,返回一个 16 位结果,那么这个 CPU 需要一个“无符号加法”和一个“有符号加法”指令!

    乘法也是如此。然而,很多(大多数)CPU 是这样工作的:它们将两个 32 位数字相乘,结果不是低 32 位,而是完整的 64 位——因此需要“有符号乘法”和“无符号乘法”指令。

    然而,很少有 CPU 将两个 32 位数字相乘,只返回结果的低 32 位。这些 CPU 只需要一条“乘法”指令。

    部门

    除以 8 与右移三位的操作相同。

    这意味着左侧的三个隐式数字将被移位,这意味着左侧的隐式数字对结果有影响。

    因此,您总是需要“无符号除法”和“有符号除法”操作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-13
      • 1970-01-01
      • 1970-01-01
      • 2015-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-03
      相关资源
      最近更新 更多