如何整数除数圆负数*向下*?

时间:2010-06-15 00:58:10

标签: c# c++

似乎每当我将负int除以正int时,我需要它将向下(向-inf)舍入,而不是向0.但是C#和C ++都向0舍入。

所以我想我需要一个DivideDownward()方法。我可以用几行来写一个负面的测试,依此类推,但我的想法看起来很糟糕。所以我想知道我是否遗漏了一些东西,如果你有一种“优雅”的方式来向下舍入负分裂。

14 个答案:

答案 0 :(得分:11)

  

每当我将负int除以正int时,我需要它将向下

这是地狱,不是吗? Knuth写道,为什么这是正确的做事方式,但我们仍然坚持传统的整数硬件。

  • 如果能够承受精度的损失,最简单和最干净的方法是将32位整数转换为64位double并使用FP舍入模式舍入将商转换回整数时减去无穷大。今天的浮点单位非常快,实际上可以将更快除以整数单位;可以肯定的是,你必须要衡量。

  • 如果您需要完整的64位整数精度,我通过执行两个条件分支将这个问题作为编译器编写者处理,以便最后分割大小,然后获得正确的符号。但是,当条件分支与分裂相比便宜时,这已经有一段时间了;在今天的硬件上,我必须先试验才能推荐一些东西。

  • 原则上,您可以使用传统的英特尔80位浮点数在64位整数上提取浮点技巧,但它非常不可移植,我不相信英特尔会继续制作那个单位快。这些天浮点速度在SSE单位。

  • 寻找其他技巧的地方包括Hank Warren的书Hacker's Delight(我的副本在工作)和标准ML的MLton编译器,它需要整数除法向无穷大舍入

无论你做什么,当你确定它时,如果你使用的是C ++或C99,请将你的除法程序粘贴到.h文件中并使其成为static inline。这样,当您的解决方案对于5年内交付的新的硬件硬件来说不是最理想的时候,您就有一个地方可以对其进行更改。

答案 1 :(得分:8)

你可以通过这样做摆脱任何分支:

inline int DivideRoundDown(int a_numerator, int a_denominator)
{
    return (a_numerator / a_denominator) + ((a_numerator % a_denominator) >> 31);
}

答案 2 :(得分:7)

假设b总是正数,这是一个廉价的解决方案:

inline int roundDownDivide(int a, int b) {
  if (a >= 0) return a/b; 
  else return (a-b+1)/b;
}

答案 3 :(得分:6)

如果你想以相对简洁的方式使用整数来写这个,那么你可以这样写:

var res = a / b - (a % b < 0 ? 1 : 0);

这可能会编写很多指令,但它可能仍然比使用浮点更快。

答案 4 :(得分:5)

警告:对于输入a = -1,此帖子会产生错误的结果。请参阅其他答案。 -Cam


[C ++]

这可能就是你所说的'kludgey',但这是我想出来的;)

int divideDown(int a, int b){
    int r=a/b;
    if (r<0 && r*b!=a)
        return r-1;
    return r;
}

在if语句中,我把r&lt; 0 - 但是我不确定这是不是你想要的。您可能希望将if语句更改为

if (a<0 && b>0)

这与您的描述一致“似乎每当我将负数int除以正数”时。

答案 5 :(得分:3)

Math.Floor((double)a/(double)b)
如果你需要它作为一个int,之后施放它 (int)Math.Floor((double)a/(double)b)

答案 6 :(得分:2)

对于2的幂,取决于您的编译器实现(Visual Studio),您可以使用右移。

-5 >> 2 == -2

答案 7 :(得分:2)

许多编程语言,包括旧的C和C ++标准,都保证了除法规则,即

a = b * (a / b) + a % b

即使a/ba%b的确切值未定义。 (现在它们是用C和C ++定义的。)这可以被利用来使用(相当于)以下代码在许多语言和平台中计算所需的结果:

int divF(int a, int b) { return a / b - (a % b < 0); }

这是来自@ TomasPetricek答案的版本。但是,这仅适用于b > 0

以下代码适用于任何b != 0 [1]

int sign(int x) { return (x > 0) - (x < 0); }
int divF2(int a, int b) { return a / b - (sign(a % b) == -sign(b)); }

然而,四舍五入的部门(又名地板部门,又名Knuth的部门)并不总是令人满意的。有人认为 [2] 是欧几里德分裂最普遍有用的分支。它向下舍入为b > 0,向上舍入为b < 0。对于所有ab,它具有良好的属性,即兼容定义的余数的值总是非负的,与它们的符号无关。此外,它与两个除数除数的二进制补码机上的位移相吻合。是的,计算速度也快:

int divE(int a, int b) {
    int c = a % b < 0;
    return a / b + (b < 0 ? c : -c);
}

所有三个版本都在amd64上使用clang 3.4.1 -O2生成无分支代码。但是,在二进制补码架构中,以下可能稍微快一些:

int divE2(int a, int b) { return a / b + (-(a % b < 0) & (b < 0 ? 1 : -1)); }

生成的代码

DIVF:

cdq
idiv esi
shr edx, 31
sub eax, edx

divF2:

cdq
idiv esi
test edx, edx
setg cl
movzx ecx, cl
shr edx, 31
sub ecx, edx
test esi, esi
setg dl
movzx edx, dl
shr esi, 31
sub esi, edx
cmp ecx, esi
sete cl
movzx ecx, cl
sub eax, ecx

潜水:

cdq
idiv esi
mov ecx, edx
shr ecx, 31
sar edx, 31
test esi, esi
cmovs edx, ecx
add eax, edx

divE2:

cdq
idiv esi
sar edx, 31
shr esi, 31
lea ecx, dword ptr [rsi + rsi - 1]
and ecx, edx
add ecx, eax
mov eax, ecx // WTF? But OK when inlined...

基准

使用上面提到的编译器,在i7-3720QM CPU @ 2.60GHz上,使用不同的TESTFUNC运行以下代码,给出

simple truncating division:
1225053229                 base:  8.41 ns

round to -inf for b > 0, broken for b < 0:
 975205675                 divF:  8.70 ns
 975205675               ben135:  8.71 ns

euclidean division:
1225082031                divE2:  9.37 ns
1225082031                 divE:  9.67 ns

round to -inf, work for b < 0:
 975229215                Chazz:  9.67 ns
 975229215                divF2: 10.56 ns
 975229215           runevision: 13.85 ns
 975229215        LegsDrivenCat: 16.62 ns
 975229215                Warty: 20.00 ns

likely an overflow bug trying to implement the above:
 975049659              DrAltan: 11.77 ns

第一列是校验和分组算法,产生相同的结果。 base是用于衡量测试开销的最简单的截断分区。

代码:

#define STRINGIZE2(a) #a
#define STRINGIZE(a) STRINGIZE2(a)

int main()
{
    srandom(6283185);
    static const int N = 500000000;
    int ret = 0;
    for(int i = 0; i < N; ++i)
    {
        int a = random() - 0x40000000, b;
        do b = (random() >> 16) - 0x4000; while(b == 0);
        ret += TESTFUNC(a,b);
    }

    timespec t;
    clock_gettime(CLOCK_VIRTUAL, &t);
    printf("%10d %20s: %5.2f ns\n", ret, STRINGIZE(TESTFUNC), (t.tv_sec*1.e9 + t.tv_nsec)/N);
}

参考

  1. Daan Leijen. Division and Modulus for Computer Scientists. December 2001.
  2. Raymond T. Boute. The Euclidean definition of the functions div and mod. April 1992.

答案 8 :(得分:1)

这是一个在除数为负数时有效的版本:

int floor_div2(int a, int b)
{
  int rem = a % b;
  int div = a/b;
  if (!rem)
    a = b;
  int sub = a ^ b;
  sub >>= 31;
  return div+sub;
}

答案 9 :(得分:1)

上面的Atlans博士解决方案是最常见的答案,但是如果你知道an不会小于某个固定数字,你可以添加b的最小倍数,使其成为正数,然后调整结果。例如,如果a始终>> = -10 * b,则可以说:

return (a + 10 * b) / b - 10

或者如果你只是想要一个正数结果,当a为正数或0和某些否定结果当a为负数时(例如,如果测试结果是> = 0而不包括范围a = [-1; -b +1])你可以做到

return (a + b) / b - 1;

将为[-1,-b + 1]和[-b; -2 * b + 1],但如果你只是尝试建立,如果分裂的结果为正,则无关紧要。实际上,我们只是改变舍入,使其向-1舍入而不是0。

答案 10 :(得分:1)

我不是低级优化方面的专家,但这里的版本没有分支(条件),也不需要转换为浮点数。在C#中测试,它似乎比使用分支的版本快得多。

return (a - (((a % b) + b) % b)) / b;

此解决方案部分来自关于如何为负数做最佳模函数的讨论:Modulo of negative numbers

答案 11 :(得分:0)

修改:我的解决方案并非在所有情况下均有效。这是Dr.Atlan's解决方案的C#实现:

/// <summary>
/// Divides a/b, rounding negative numbers towards -Inf.
/// </summary>
/// <param name="a">Dividend</param>
/// <param name="b">Divisor; must be positive for correct results</param>
/// <returns>a ÷ b</returns>
public static int Div(int a, int b)
{
    return a < 0 ? (a - b + 1) / b : a / b;
}

答案 12 :(得分:0)

通过偏移2 ^ 31进行无符号长整数计算,例如:

int floor_div(int a, int b)
{
  unsigned long aa = a + (1UL << 31);
  unsigned long cc = (aa / b) - (1UL << 31);
  return (int)cc
}

我没有测试过这个。

答案 13 :(得分:0)

我是这样做的(只有一个分区,没有乘法,没有模数,看起来像最快的解决方案):

(a < 0 && b > 0) ? (a - b + 1) / b : (a > 0 && b < 0) ? (a - b - 1) / b : a / b

我很好奇什么是更快:更少的分支或更少的乘法/除法/模数,所以我将我的解决方案与 runevision 进行比较,在这种特殊情况下,较少的划分比较少的划分更好。 ben135 的解决方案在某些情况下会给出不正确的结果(例如10 / -3给出-3但应该是-4)。