【问题标题】:What are widening integer operations?什么是扩大整数运算?
【发布时间】:2021-07-20 17:42:54
【问题描述】:

让我们以AddMultiply 为例。哪一项可以归类为加宽

假设输入是 2 的补码中的有符号字符(即长度为 8 位),除非另有声明。

正数加法

1+2 = 3

这个操作似乎没有扩大。 1、2 和 3 都适合一个角色。

但是,250 + 6 会溢出无符号字符。那么这是扩大范围了吗?

签名类型也可以这样做,125 + 5 将带符号的字符溢出到符号位中。这是在扩大吗?

负数加法

-2-3 = -5

这会使二进制中的字符溢出 1 位:

   1 1 1 1 1 1 1 0
+  1 1 1 1 1 1 0 1
------------------ 
 1 1 1 1 1 1 0 1 1 

溢出通常会被丢弃,但是,这是否被认为是扩大操作?

正数乘法

1 * 2 = 2

所有乘法是否都在扩大,即使结果仍然适合原始数据类型?

上面的例子,2 仍然适合 8 位字符,但是,如果我用二进制手动进行数学运算,额外的 0s 会附加到结果的左侧,从而被丢弃。

负数乘法

-2 * -3 = 6

为简单起见,假设我们的输入是 4 位。这溢出了预期大小的两倍,即结果变为 8 位:

1110 * 1101 = 10110110

高4位被丢弃,低4位等于6

这算不算加宽操作?

分部

我没有详细介绍除法,但我认为它被认为是一种缩小操作。

【问题讨论】:

  • 你对“扩大”的定义是什么?
  • 我没有,这主要是个问题,已知的加宽是什么?
  • 从 8+9=17 开始,不就是说加法“加宽”了吗?
  • 好点,我将它添加到正数加法中。
  • 很多地方,例如:stackoverflow.com/a/68041066/14940626, Widening signed-multiply doesn't involve any truncation...

标签: assembly cpu-architecture twos-complement integer-arithmetic


【解决方案1】:

是否加宽也是执行数学运算的汇编指令的一个属性。但是,提供什么指令的选择取决于数学运算的固有属性,例如这种乘法可以产生更广泛的结果。


在数学上,两个n位数字的和或差最多需要n+1位才能准确表示。例如无符号 3 位或二进制补码 4 位 7+7 = 14,即 0b1110 无符号或 0b01110 二进制补码。

大多数 CPU 仅提供add/sub 的非扩展实现,如果有的话,额外的位将进入进位标志。 (一些像 MIPS 这样的 ISA 根本没有 FLAGS,所以如果你想要结转,你必须从 carry = (a+b) < a 恢复它(无符号比较)。


在数学上,两个 n 位数字的乘积最多需要 2n 位才能准确表示。例如带符号的 8 位 -128 * -128 = 16384 SCHAR_MIN 平方(即 0x80 * 0x80 = 0x4000,请注意结果中的符号位为 0,但正下方的位被设置)。或无符号 8 位 255 * 255 = 650250xff ^ 2 = 0xfe01

许多 CPU 确实提供了一种扩展的乘法形式,例如x86 的 mul r/m32imul r/m32 将产品写入 EDX:EAX。

x86 还具有非扩展 imul r, r/m32 以防您不关心高半部分,例如为unsigned * unsigned 实现C 语义,产生unsigned 结果,截断完整结果。 (请注意,无论将输入解释为有符号还是无符号,低半部分都是相同的,只有高半部分不同。这就是 x86 不费心提供类似的mul r, r/m32 指令的原因)

一些 ISA 不提供一个扩展乘法,而是提供单独的低半和有符号高半/无符号高半指令。 (背靠背运行它们可能会让一些实现在内部将它们融合成一个单一的加宽乘法,但更简单的实现可以单独运行它们,软件可能只使用其中一个。)

某些 ISA 甚至可能根本没有提供获得高一半的方法,例如ARM Cortex-M0 微控制器只有 32b x 32b => 32b 乘法,没有umull。将其用作更广泛的乘法(如 64x64 => 128 位)的构建块可能意味着将输入分解为 16 位块,以在 16b x 16b => 32b 乘法方面应用通常的扩展精度技术。


在数学上,整数除法最多可以产生一个被除数宽度的商。对于扩展精度目的,支持更广泛的红利很有用(请参阅Why should EDX be 0 before using the DIV instruction?),但编译器通常不使用此功能。 (特别是在 x86 等 ISA 上,如果商不适合 32 位,则 64b / 32b => 32b 除法将出错。)

但是是的,如果 ISA 有一个宽除数的除法指令,例如 x86 的 dividiv 的隐式 EDX:EAX,则称为窄除法。

【讨论】:

  • 谢谢,所以溢出(即int加法溢出)并没有扩大,只是溢出。
  • @Dan:如果您使用add eax, ecx 之类的指令来执行此操作,那么是的,这是真的。但是,在有符号溢出或无符号进位情况下,数学完整的非截断结果比寄存器宽。但通常你会在 asm 中处理这个问题,方法是在添加 before 之前对输入进行符号或零扩展,以避免包装。
  • @Dan:如果您要单独执行一位操作来实现二进制加法,那么您需要一个额外的输出位来保存结果而不会丢失任何内容。如果您要与 SIMD 并行执行 256 个单独的位,这实际上是一个有用的想法,例如用于 popcount (github.com/WojciechMula/sse-popcount/blob/…) 的 Harley-Seal 技术,您在执行 8 * popcnt(eights) + 4 * popcnt(fours) + ... 之前垂直添加位几个步骤。您可以执行多少个步骤而不溢出取决于有多少个变量。
  • @Dan:是的,它在 CF:reg 中捕获完整的结果,用于具有进位标志的 CPU。但是完整的结果并不都在一个寄存器中,所以我不会像乘法指令一样将其称为“加宽”。
  • @Dan:你所说的问题是关于“溢出位进入符号位的位置”的部分。您说的是“溢出 bit”,但英特尔只是将“溢出”作为一个单独的布尔条件(例如,它将进入 OF),而不是可以有意义地放入整数寄存器的位其他相加结果。当然不是作为加法的符号位。
【解决方案2】:

加宽/乘法表示运算的结果大于至少一个输入。

特别是在 arm neon 中,扩展运算符与 (en)longening 指令不同,因此扩展加法/累加将 8 位源添加到 16 位目标,而长加法将两个 8 位源添加到 16 位目标。这同样适用于 16 位和 32 位源。

0001+0002==00000003 加宽。

同样 01*02==0002 加宽和 02 不加宽。

在 Intel x86 中,ISA 乘法被设计为扩展,产生 32 位输出到寄存器对 dx:ax。同样,IA 的划分也在缩小。

对比 c/c++ 语义,如果 int32/uint32 算术主要是模块化的,则丢弃无限精度结果的高位。

【讨论】:

  • x86 也有非扩展乘法:imul reg, reg-or-memimul r, r/m, immediatefelixcloutier.com/x86/imul。如果您不关心高半部分,则不需要破坏 [er]dx:[er]ax 进行乘法运算,除非您仅使用 x86 的 8086 兼容子集。 (这些表格分别在 386 和 186 中是新的,IIRC)
  • 确实,SSE(2) 指令集添加了指令来获取长乘法的最高位。 NEON 也有缩小加和返回高半部分。
  • 仅高半非扩展乘法仅存在于 SSE2 中,用于 16 位元素,pmulhw / pmulhuw 作为 pmulld 的高半,以及 SSSE3 pmulhrsw 固定-点舍入乘法,它采用接近完整结果顶部的 16 位位窗口。对于 32 位元素,我们有 32x32 => 64 位扩展 SIMD 乘法 (pmuludq),但在 AVX-512IFMA52 vpmadd52huqluq 整数 MAC 之前不是纯高半部分。
猜你喜欢
  • 1970-01-01
  • 2012-11-04
  • 2013-06-23
  • 1970-01-01
  • 1970-01-01
  • 2012-05-11
  • 2013-07-31
  • 1970-01-01
相关资源
最近更新 更多