将整数除以3的最快方法是什么?

时间:2008-10-05 01:13:20

标签: optimization bit-manipulation division

int x = n / 3;  // <-- make this faster

// for instance

int a = n * 3; // <-- normal integer multiplication

int b = (n << 1) + n; // <-- potentially faster multiplication

12 个答案:

答案 0 :(得分:119)

那个说“把它留给编译器”的人是对的,但我没有“声誉”来修改他或评论。我问gcc编译int test(int a){return a / 3;对于ix86然后反汇编输出。仅仅为了学术兴趣,它正在做的是大致乘以0x55555556,然后取64位结果的前32位。您可以通过以下方式向自己展示:

$ ruby -e 'puts(60000 * 0x55555556 >> 32)'
20000
$ ruby -e 'puts(72 * 0x55555556 >> 32)'
24
$ 

Montgomery division上的维基百科页面很难阅读,但幸运的是,编译人员已经这样做了,所以你没必要这样做。

答案 1 :(得分:59)

这是最快的,因为编译器可以根据输出处理器优化它。

int a;
int b;

a = some value;
b = a / 3;

答案 2 :(得分:20)

如果您知道值的范围,有一种更快的方法,例如,如果您将有符号整数除以3并且您知道要分割的值的范围是0到768,那么您可以将它乘以一个因子,并将其向左移动2的幂,除以该因子除以3。

例如

范围0 - &gt; 768

你可以使用10位的移位,乘以1024,你想要除以3,所以你的乘数应该是1024/3 = 341,

所以你现在可以使用(x * 341)&gt;&gt; 10个
(如果使用有符号整数,确保移位是有符号的移位),同时确保移位实际上是移位而不是ROLL

这将有效地划分值3,并且在标准x86 / x64 CPU上将以约为自然除法的速度的1.6倍运行。

当然,当编译器不能进行此优化的唯一原因是因为编译器不知道X的最大范围因此无法做出此决定,但您作为程序员可以。

有时将值移动到更大的值然后执行相同的操作甚至更有利,即。如果你有一个全范围的int你可以使它成为一个64位的值,然后进行乘法和移位而不是除以3。

我最近必须这样做以加快图像处理速度,我需要找到3个颜色通道的平均值,每个颜色通道都有一个字节范围(0 - 255)。红绿蓝。

起初我只是简单地使用:

avg =(r + g + b)/ 3;

(所以r + g + b的最大值为768,最小值为0,因为每个通道的字节数为0 - 255)

经过数百万次迭代后,整个操作耗时36毫秒。

我将行改为:

avg =(r + g + b)* 341&gt; 10;

这使它降到22毫秒,这可以通过一点点聪明才能实现。

这种加速发生在C#中,即使我已启用优化并且本机运行该程序而没有调试信息而不是通过IDE。

答案 3 :(得分:11)

有关更有效地除以3的扩展讨论,请参阅How To Divide By 3,专注于进行FPGA算术运算。

也相关:

答案 4 :(得分:10)

取决于您的平台并依赖于您的C编译器,本机解决方案就像使用

一样
y = x / 3

可以很快或者速度非常慢(即使分割完全在硬件中完成,如果使用DIV指令完成,该指令比现代CPU上的乘法慢大约3到4倍)。打开优化标记的非常好的C编译器可以优化此操作,但如果您想确定,最好自己优化它。

对于优化,重要的是具有已知大小的整数。在C int中没有已知的大小(它可能因平台和编译器而异!),因此您最好使用C99固定大小的整数。下面的代码假设您要将无符号的32位整数除以3并且C编译器知道64位整数(注意:即使在32位CPU架构上,大多数C编译器也可以处理64位整数细):

static inline uint32_t divby3 (
    uint32_t divideMe
) {
    return (uint32_t)(((uint64_t)0xAAAAAAABULL * divideMe) >> 33);
}

这听起来很疯狂,但上面的方法确实除以3.它所需要的只是一个64位乘法和一个移位(就像我说的,乘法可能比分区快3到4倍)在你的CPU)。在64位应用程序中,此代码将比32位应用程序快得多(在32位应用程序中,将两个64位数字相乘,在32位值上进行3次乘法和3次加法) - 但是,它可能仍然比在32位机器上划分。

另一方面,如果你的编译器非常好并且知道如何通过常量优化整数除法(最新的GCC,我刚刚检查过),它将生成上面的代码(GCC将准确创建)如果您至少启用优化级别1,则此代码为“/ 3”。对于其他编译器......你不能依赖或期望它会使用这样的技巧,即使这种方法有很好的记录并且在因特网上随处可见。

问题是它只适用于常数,而不适用于变数。你总是需要知道幻数(这里是0xAAAAAAA)和乘法后的正确操作(大多数情况下是移位和/或加法),两者都有所不同,具体取决于你想要除以的数字,两者都占用了太多的CPU时间。在运行中计算它们(这将比硬件部分慢)。但是,编译器很容易在编译期间计算这些内容(其中一秒或多或少的编译时间几乎不起作用)。

答案 5 :(得分:3)

如果你确实不想乘法或除法怎么办?这是我刚刚发明的近似值。它起作用,因为(x / 3)=(x / 4)+(x / 12)。但是因为(x / 12)=(x / 4)/ 3,我们只需要重复这个过程,直到它足够好。

#include <stdio.h>

void main()
{
    int n = 1000;
    int a,b;
    a = n >> 2;
    b = (a >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    printf("a=%d\n", a);
}

结果是330.使用b =((b + 2)>&gt; 2)可以使其更准确;考虑四舍五入。

如果 允许相乘,只需选择适当的近似值(1/3),并使用2的幂除数。例如,n *(1/3)〜= n * 43/128 =(n * 43)>&gt; 7。

此技术在Indiana.

中最有用

答案 6 :(得分:2)

我不知道它是否更快但是如果你想使用按位运算符来执行二进制除法,你可以使用this page中描述的shift和subtract方法:

  
      
  • 将商设为0
  •   
  • 对齐被除数和除数中最左边的数字
  •   
  • 重复:      
        
    • 如果除数以上的那部分股息大于或等于除数:      
          
      • 然后从被除数的那部分中减去除数,
      •   
      • 将商品1连接到商的右手边
      •   
      • Else将0连接到商的右手端
      •   
    •   
    • 将除数向右移动
    •   
  •   
  • 直到红利低于除数:
  •   
  • 商是正确的,被除数是余数
  •   
  • 停止
  •   

答案 7 :(得分:2)

对于64位数字:

uint64_t divBy3(uint64_t x)
{
    return x*12297829382473034411ULL;
}

然而,这不是您可能期望的截断整数除法。 如果数字已经被3整除,它就可以正常工作,但是如果它不是,则返回一个很大的数字。

例如,如果您在例如11上运行它,它将返回6148914691236517209.这看起来像垃圾,但它实际上是正确的答案:将它乘以3然后你回到11!

如果您正在寻找截断分区,那么只需使用/运算符即可。我非常怀疑你能比这更快。

理论值:

64位无符号算术是模2 ^ 64算术。 这意味着对于每个与2 ^ 64模数(基本上所有奇数)相互作用的整数,存在乘法逆,您可以使用乘法而不是除法。可以通过使用扩展欧几里德算法求解3*x + 2^64*y = 1方程来获得该幻数。

答案 8 :(得分:1)

如果你真的想在integer division上看到这篇文章,但它只具有学术价值......那将是一个有趣的应用,实际上需要从这种技巧中受益。

答案 9 :(得分:1)

对于非常大的整数除法(例如,大于64位的数字),您可以将您的数字表示为int []并通过一次取两位数并将它们除以3来快速执行除法。剩余部分将是接下来的两位数字等等。

例如。 11004/3你说

11/3 = 3,剩磁者= 2(从11-3 * 3)

20/3 = 6,余数= 2(从20-6 * 3)

20/3 = 6,余数= 2(从20-6 * 3)

24/3 = 8,余数= 0

因此结果 3668

internal static List<int> Div3(int[] a)
{
  int remainder = 0;
  var res = new List<int>();
  for (int i = 0; i < a.Length; i++)
  {
    var val = remainder + a[i];
    var div = val/3;

    remainder = 10*(val%3);
    if (div > 9)
    {
      res.Add(div/10);
      res.Add(div%10);
    }
    else
      res.Add(div);
  }
  if (res[0] == 0) res.RemoveAt(0);
  return res;
}

答案 10 :(得分:0)

易于计算...最多n次迭代,其中n是您的位数:

uint8_t divideby3(uint8_t x)
{
  uint8_t answer =0;
  do
  {
    x>>=1;
    answer+=x;
    x=-x;
  }while(x);
  return answer;
}

答案 11 :(得分:0)

在某些架构中,查找表方法也会更快。

uint8_t DivBy3LU(uint8_t u8Operand)
{
   uint8_t ai8Div3 = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, ....];

   return ai8Div3[u8Operand];
}