7

有没有办法删除以下 if 语句来检查值是否低于 0?

int a = 100;
int b = 200;
int c = a - b;

if (c < 0)
{
    c += 3600;
}

的值c应介于 0 和 3600 之间。两者ab都有符号。的值a也应该介于 0 和 3600 之间。(是的,它是 0.1 度的计数值)。该值被中断重置为 3600,但如果该中断来得太晚,它会下溢,这不是问题,但软件应该仍然能够处理它。它确实如此。

if (c < 0)我们在很多我们计算头寸的地方做这个检查。(计算新位置等)

我习惯于使用 python 模运算符来使用除数的符号,而我们的编译器(C89)正在使用除数符号。

有什么方法可以不同地进行这个计算吗?示例结果:

 a  -  b  = c
100 - 200 = 3500  
200 - 100 = 100
4

5 回答 5

11

好问题!这个怎么样?

c += 3600 * (c < 0);

这是我们保留分支预测器槽的一种方式。

于 2013-07-19T15:37:11.047 回答
7

这个怎么样(假设是 32 位整数):

c += 3600 & (c >> 31);

c >> 31将所有位设置为原始 MSB,负数为 1,其他 2-补码为 0。

负数右移是根据 C 标准文档正式实现定义的,但是它几乎总是通过 MSB 复制实现(普通处理器可以在单个指令中完成)。

这肯定会导致没有分支,(c < 0)这与在某些情况下可能使用分支实现不同。

于 2013-07-19T21:13:11.127 回答
5

你为什么担心分支?[原因在问题的评论中解释。]

另一种方法是:

((a - b) + 3600) % 3600

这假设a并且b已经在范围内0..3600;如果它们不受控制,更通用的解决方案是Drew McGowen 建议的:

((a - b) % 3600 + 3600) % 3600

分支未命中必须非常昂贵才能使这么多计算值得。

于 2013-07-19T15:29:09.980 回答
4

@skjaidev 展示了如何在没有分支的情况下做到这一点。int以下是当s 是二进制补码时如何自动避免乘法:

#if ((3600 & -0) == 0) && ((3600 & -1) == 3600)
c += 3600 & -(c < 0);
#else
c += 3600 * (c < 0);
#endif
于 2013-07-19T20:49:47.723 回答
0

你想做的是模运算。你的 2 的补码机器已经用整数数学做到了这一点。因此,通过将您的值映射到 2 的补码算术,您可以免费获得 modolo 操作。

诀窍是将您的角度表示为 0 到 1-epsilon 之间 360 度的分数。当然,那么您的恒定角度必须以类似方式表示,但这应该不难;它只是我们可以隐藏在转换函数(呃,宏)中的一点数学。

这个想法的价值在于,如果你添加或减去角度,你会得到一个你想要的小数部分和你想要丢弃的整数部分的值。如果我们将分数表示为 32 位定点数,二进制点为 2^32(例如,在通常被认为是符号位的左侧),分数的任何溢出都会简单地从免费的 32 位值。所以,你做所有的整数数学,“溢出”删除是免费的。

所以我会重写你的代码(保留度数乘以 10 的概念):

  typedef unsigned int32 angle; // angle*3600/(2^32) represents degrees
  #define angle_scale_factor 1193046.47111111 // = 2^32/3600
  #define make_angle(degrees)  (unsigned int32)((degrees%3600)*angle_scale_factor ) 
  #define make_degrees(angle) (angle/(angle_scale_factor*10)) // produces float number

  ...

  angle a = make_angle(100);  // compiler presumably does compile-time math to compute 119304647 
  angle b = make_angle(200);  // = 238609294
  angle c = a - b; // compiler should generate integer subtract, which computes 4175662649

  #if 0 // no need for this at all; other solutions execute real code to do something here
  if (c < 0)  // this can't happen
     { c += 3600; } // this is the wrong representation for our variant
  #endif


  // speed doesn't matter here, we're doing output:
  printf("final angle %f4.2 = \n", make_degrees(c)); // should print 350.00

我还没有编译和运行这段代码。

使这个度数乘以 100 或乘以 1 非常容易;修改 angle_scale_factor。如果你有一台 16 位机器,切换到 16 位同样容易;如果您有 32 位,但仍只想进行 16 位数学运算,则需要将要打印的值屏蔽为 16 位。

这个解决方案还有一个很好的属性:您已经记录了哪些变量是角度(并且有有趣的表示)。OP 的原始代码只是将它们称为整数,但这不是它们所代表的;未来的维护者会对原始代码感到惊讶,尤其是当他发现减法与变量隔离时。

于 2013-07-21T04:50:45.050 回答