【问题标题】:Fast Multiplication快速乘法
【发布时间】:2009-10-21 12:34:24
【问题描述】:

我正在为具有快速整数运算和不那么快浮点运算的微处理器编写代码。我需要将一个整数除以一个 1 到 9 的数字,然后将结果转换回整数。

我创建了一个浮点数组,其中包含 0、1、0.5、0.3333 等成员。 但我认为除了 (1/3) 之外的数字有 MAGIC 常量(如 0x55555556)。

这个数字是多少?

【问题讨论】:

  • “从1到9的数”不是整数吗?
  • 如果问题陈述不好,我会给 -1,但对于代表为 1 的人来说,这有点浪费......
  • 如果要将浮点数转换回整数,为什么还需要浮点数?
  • 能否请您重新表述您的问题?我有点失落......

标签: c integer microcontroller multiplication


【解决方案1】:

如果您的微控制器上的除法指令足够快,请使用它。如果您需要结果的小数部分,您可以使用余数;在大多数体系结构中,除法指令将商放在一个寄存器中,将余数放在另一个寄存器中。

如果您的除法指令不够快但乘法指令足够快,您可以使用以下技术(听起来好像这是您所追求的技术)。在大多数架构上,将一个 32 位数字乘以另一个 32 位数字会得到 64 位结果;较高的一半存储在一个寄存器中,而较低的一半存储在另一个寄存器中。您可以通过意识到除以数字 n 与乘以 (2^32)/n 相同,然后取结果的更重要的 32 位来利用这一点。换句话说,如果你想除以 3,你可以改为乘以 0x100000000/3 = 0x55555555,然后取结果中更高的 32 位。

您在这里所做的实际上是一种定点算术。请查看Wikipedia article 了解更多信息。

【讨论】:

  • 非常感谢。这个微处理器是飞利浦 PNX1500(很旧——我是为了教育目的而买的)。处理器的除法非常慢(它没有整数除法 - 只有浮点数)。例如:我用乘法交换除法运算并获得大约 2.25 倍的加速度。你的回答对我帮助很大。
  • 我刚试过你的方法。但是在输出视频(我正在处理视频)上,我得到了奇怪的伪影(它是帧上的黑点)。我使用了以下数组:静态 UInt32 lookup_for_multiply[10] = {0, 1, 0x80000000, 0x55555555, 0x40000000, 0x33333333, 0x2AAAAAAA, 0x24924924, 0x20000000, 0x1C71C71C};我哪里错了?
  • 好的。我得到了它。第一个成员应该我 0xFFFFFFFF 买而不是 0x1。
【解决方案2】:

我假设,基于微控制器标签,您没有快速整数除法。我的答案也适用于无符号值 - 它适用于有符号值,您只需要限制下面棘手的部分中使用的数字。

一个好的开始是除以 2、4 和 8。假设您的 CPU 具有逻辑右移指令,这些可以分别通过右移 1、2 和 3 位来完成。

其次,除以 1 只是保持数字不变。只剩下 3、5、6、7 和 9。

棘手的部分从这里开始:

对于其他数字,您可以使用除法可以替换为乘法和移位的事实。

假设您有一个 16 位处理器。要除以 N,乘以 256/N 并右移 8 位:

N = 3, multiply by 85
N = 5, multiply by 51
N = 6, multiply by 43
N = 7, multiply by 37
N = 9, multiply by 28

以 72 / 5 为例。将 72 乘以 51 得到 3672,然后右移 8 位得到 14。

为了使其正常工作,您使用的数字不得溢出 16 位。由于最坏的情况是乘以 85,因此您最多可以处理 771 的数字。

之所以可行,是因为 8 位右移与除以 256 相同,并且:

  m * (256 /  n) / 256
= m / (n /  256) / 256
= m /  n *  256  / 256
= m /  n * (256  / 256)
= m /  n

如果您有 32 位处理器,则值和范围会有所变化,因为它是 65536/N:

N = 3, multiply by 21,846, right shift 16 bits, max value roughly 196,600.
N = 5, multiply by 13,108.
N = 6, multiply by 10,923.
N = 7, multiply by  9,363.
N = 9, multiply by  7,282.

再次,让我们选择随机的 20,000 / 7:20,000 乘以 9,363 是 187,260,000,当您右移这 16 位时,您会得到 2,857 - 实际结果是 2,857。

以下 C 语言测试程序显示了给定值的准确度数字。它使用有符号值,因此最多只能达到 98,000 左右,但您可以看到最大误差为 1,并且它发生在 13,110 的低点(只有 0.008% 的误差)。

#include <stdio.h>
int res[5] = {0};
int low[5] = {-1,-1,-1,-1,-1};
int da[] = {3,5,6,7,9};
int ma[] = {21846,13108,10923,9363,7282};
int main (void) {
    int n, i;
    for (n = 0; n < 98000; n++) {
        for (i = 0; i < sizeof(da)/sizeof(da[0]); i++) {
            int r1 = n / da[i];
            int r2 = (n * ma[i])>>16;
            int dif = abs (r1-r2);
            if (dif >= 5) {
                printf ("%d / %d gives %d and %d\n", n, da[i], r1, r2);
                return 1;
            }
            res[dif]++;
            if (low[dif] == -1) {
                low[dif] = n;
            }
        }
    }
    for (i = 0; i < sizeof(res)/sizeof(res[0]); i++) {
        printf ("Difference of %d: %6d, lowest value was %6d\n", i, res[i], low[i]);
    }
    return 0;
}

这个输出:

Difference of 0: 335874, lowest value was      0
Difference of 1: 154126, lowest value was  13110
Difference of 2:      0, lowest value was     -1
Difference of 3:      0, lowest value was     -1
Difference of 4:      0, lowest value was     -1

【讨论】:

  • 除以 3, 5, 6, 7, 9 是一个真正的问题,因为微处理器具有超标量架构,每个 if 对它来说都是一个真正的问题。
  • 查看更新,@georgethegreat。如果数字足够小,则根本不需要选择 - 您可以使用乘法和移位。
  • 我做了类似的事情(我有 32 位处理器)。但是我在视频中发现了一些奇怪的人工制品,我正在处理。当此方法给出错误结果时是否有任何异常?我对之前的答案做了更详细的评论 - 请阅读。
  • 不,只要您不超过最大允许值(因此溢出),错误应该非常小。如果您想检查输出,您实际上可以编写一个小程序,使用此方法和对从 0 到 196,611 的每个值进行实除以确保它正常工作。
  • @Georg(e?),我已经发布了一些 C 代码来测试这些值(并且每个值都增加了一个,因为当我应该四舍五入时我已经四舍五入了)。它显示了该方法的准确度(非常)。
猜你喜欢
  • 2017-01-25
  • 1970-01-01
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 2010-12-08
  • 1970-01-01
  • 2015-03-02
  • 2015-04-07
相关资源
最近更新 更多