有人可以解释这个代码 - rtc用bitwise(在c中)滚动?

时间:2012-12-19 20:23:01

标签: c bit-manipulation

任何人都可以解释这个((〜(preRtcInseconds<< 1)+1)>>> 1)

unsigned long preInseconds;
unsigned long curInSeconds;
unsigned long elapsedInSeconds;

if(curInSeconds>=preInseconds)
{
   elapsedInSeconds = curInSeconds - preInseconds;//does easy thing. no needs roll over
}
else{ //rollover
  preInseconds = ((~(preInseconds <<1)+1)>>1);
  elapsedInSeconds = curInSeconds + preInseconds;
}

3 个答案:

答案 0 :(得分:4)

unsigned long的宽度为w。那么unsigned long可以容纳的最大值是

ULONG_MAX = 2^w - 1

preInseconds = a + b,其中

a = preInseconds & (1ul << (w-1))
b = preInseconds & ((1ul << (w-1)) - 1)

a为0或2^(w-1),具体取决于preInseconds >= 2^(w-1)。然后,最初的左移湮灭了a,所以

preInseconds << 1

提供b << 12*b

然后采用按位补码,

~(b << 1)

给出ULONG_MAX - (b << 1)

如果b == 0,则向ULONG_MAX - (b << 1)添加1会导致0,否则

~(b << 1) + 1

给出

2^w - (b << 1)

如果b == 0

,则向右移一位会产生0
2^(w-1) - b

否则。因此

preInseconds = ((~(preInseconds <<1)+1)>>1);

preInseconds设置为

2^(w-1) - b

如果b != 0,则{b == 0

最后,

elapsedInSeconds = curInSeconds + preInseconds;
因此,如果elapsedInSecondscurInSeconds [如果preInseconds分支被采用2^(w-1),那么

else设置为preInseconds > curInSeconds的值,所以它不是0]和

curInSeconds - (preInseconds & (ULONG_MAX >> 1)) + 2^(w-1)

否则。

我不确定该操作的目的是什么。对于preInseconds > curInSeconds,计算

curInSeconds - preInseconds

会导致

(2^w - preInseconds) + curInSeconds

与(数学上)

相同
(2^w + curInSeconds) - preInseconds

这是自上次curInSeconds采取preInseconds计数器一次以来经过的时间,这可能是因为当前计数器值小于以前的。这是有道理的。

在其他分支完成体操,

  • 如果preInseconds <= 2^(w-1)elapsedInSeconds变为curInSeconds - preInseconds + 2^(w-1),则滚动当前计数器与前一个计数器之间的差值减去2^(w-1)
  • 如果preInseconds > 2^(w-1),由于无符号算术中的x - 2^(w-1) == x + 2^(w-1),我们获得与curInSeconds - preInSeconds相同的结果。

因此,假设curInSeconds翻了一次,它会计算上一个事件和当前事件之间经过的秒数,除非在上一个事件发生后2^(w-1)或更多秒发生翻转,在这种情况下从实际经过的时间中减去2^(w-1)秒。

答案 1 :(得分:2)

它看起来像是由一个不明白C无符号算术是mod 2 ^ n(或者不理解mod 2 ^ n算术)的人编写的有问题的翻转代码。它实际上完全等同于:

elapsedInSeconds = curInSeconds - preInseconds;
if (curInSeconds < UNLONG_MAX/2 && preInseconds < ULONG_MAX/2)
    elapsedInSeconds &= ULONG_MAX/2;

也就是说,它执行mod 2 ^ n减法,但在少数情况下(在任何测试用例中可能都没有被击中),它会出现错误(清除而不是设置)。

这很有可能是故意的,因为这里发生的是如果两个数字都是&lt; 2 ^ n-1那么它会进行mod 2 ^ n-1减法,如果两个数字都是mod 2 ^ n减法number是&gt; = 2 ^ n-1(其中n是无符号长位的大小)。如果时间来自某些硬件设备,可能总是清除最高位或可能不是,这可能是有道理的。

答案 2 :(得分:1)

首先,我在下面的讨论中假设您的unsigned long类型是32位宽。如果不是,那就没关系;它的实际范围并不重要,但我的所有例子都假设它的宽度为32位。我只是使用uint32来表示,知道uint32实际上并不是uint32_t这样的标准类型,请不要打扰告诉我。

Daniel Fischer在详细解释每个低级操作方面做得很好,我在这里不再重复。但我认为你感兴趣的并不是每个低级操作的含义,而是所有这些操作作为一个组应用的必要性。正如我在下面解释的那样,无论如何都不是必需的。事实上,他们有点错误,但仅限于&#34;当前&#34;反击是围绕着“#34;之前&#34;读数。

在我到达&#34;意思&#34;之前在你的实现的数学背后,让我们首先看一下在可能的最小翻转情况下如何计算elapsedInSeconds的一个简单示例:

如果preInseconds = 0xffffffff且curInSeconds = 0x00000000,则elapsedInSeconds应为1.

preInseconds = preInseconds<<1;     // 0xfffffffe
preInseconds = ~preInseconds;       // 0x00000001
preInseconds = preInseconds+1       // 0x00000002
preInseconds = preInseconds>>1;     // 0x00000001
elapsedInSeconds = curInSeconds + preInseconds;
             ... =  0x00000000  +  0x00000001    = 1

......这正是我们所期望的。大。

然而,有趣的是,翻转处理逻辑都不是必需的。完全没有。每次我见过有人试图计算当前&#39;之间的差异。和之前的&#39;反价值,我总是看到他们跳过篮球来处理侧翻案件,而且他们经常做错了。令人遗憾的是,对于2个大小的计数器来说,使用特殊情况处理翻转是 从不 。如果计数器的满刻度(翻转点)小于数据类型的大小,则需要将结果屏蔽回计数器中的位数,但是&# 39; s是您真正需要的唯一翻转处理,在这种情况下,您每次都只需要进行屏蔽,而不必担心它是否翻过来(这也避免了分支指令,因此它更快)。由于您的翻转点是uint32的满量程值,因此您甚至不需要屏蔽结果。

这就是原因:

如上所述,假设preInseconds = 0xffffffff和curInSeconds = 0。同样,结果应为1.如果您不关心翻转,那么您只需将curInSeconds-preInseconds作为结果。但是在翻转的情况下,减法操作将产生下溢。那是什么意思?这意味着如果你有更多的位处理(即另一个uint32用作64位复合计数器的高位字),那么你需要从高位字借用1(就像等级一样) - 十进制数的学校减法)。但在你的情况下,没有更高的借词可言。没关系。真。无论如何,你都不在乎这些位。您仍然可以获得您想要的差异值:

elapsedInSeconds = curInSeconds - preInseconds;
             ... =  0x00000000  -  0xffffffff     = 1

...它给出了预期的结果,根本没有任何特殊的翻转处理逻辑。

所以你可能会想,&#34;当然,这适用于你的小例子,但如果翻滚是巨大的呢?&#34;好吧,好吧,让我们探索一下这种可能性。然后假设preInseconds = 0xffffffff和curInSeconds = 0xfffffffe。在这个例子中,我们已经将ALMOST完全包裹在前一个样本中;事实上,我们只有一个数量远离它。在这种情况下,我们的结果应该是0xffffffff(即,比uint32可以表示的值的数量少一个):

elapsedInSeconds = curInSeconds - preInseconds;
             ... =  0xfffffffe  -  0xffffffff     = 0xffffffff

不相信我?试试这个:

#include <stdio.h>
typedef unsigned long uint32;
int main()
{
    uint32 prev = 0xffffffff;
    uint32 cur  = 0xfffffffe;
    uint32 result = cur - prev;
    printf("0x%08x - 0x%08x = 0x%08x\n", cur, prev, result);
}

现在,让我们回到你的实施背后的数学:

那个计算&#34;有点&#34;计算preInseconds的两个补码,并将结果分配回preInseconds。如果您对数字的计算机表示和两次加法和减法有任何了解,您就会知道计算差值AB与计算A的总和和B的两个补码相同,即A + (-B)。如果您之前从未对其进行过调查,请查阅维基百科或任何关于两个补充如何使计算机的ALU能够重新使用其加法电路进行减法的地方。

现在实际上&#34;错误&#34;使用您已显示的代码:

要计算数字的二进制补码,可以反转数字(将其所有0位更改为1,将其所有1位更改为0),然后加1。就这么简单。那就是&#34;&#34;&#34;&#34;&#34;&#34;你的代码在做什么,但并不完全。

preInseconds = preInseconds<<1;     // oops, here we lose the top bit
preInseconds = ~preInseconds;       // do the 2's complement inversion step*
preInseconds = preInseconds+1       // do the 2's complement addition step*
preInseconds = preInseconds>>1;     // shift back to where it ought to be,
                                    // but without that top bit we wish we kept

*NOTE: The +1 above only works here because the low bit is
 guaranteed to be 1 after the ~ operation, which carries a 1
 up into the 2nd bit, where it matters.

所以我们在这里看到,基本上数学正在做的是通过执行&#34;几乎&#34;来手动否定preInseconds的值。它的两个补码转换。不幸的是,它在这个过程中也失去了最高位,这使得翻转逻辑只能工作到elapsedInSeconds = 0x7fffffff的最大值,而不是它的满量程限制为0xffffffff。

您可以将其转换为以下内容,并消除顶部位丢失:

preInseconds = ~preInseconds;       // do the 2's complement inversion step
preInseconds = preInseconds+1       // do the 2's complement addition step

所以现在你已经直接计算了两个补码,你可以计算出结果:

elapsedInSeconds = curInSeconds + preInseconds; // (preInseconds is the 2's compl of its original value)

但愚蠢的是,这在计算上等同于简单地执行此操作......

elapsedInSeconds = curInSeconds - preInseconds; // (preInseconds is its unconverted original value)

一旦你意识到这一点,你的代码示例就会变成:

if(curInSeconds>=preInseconds)
{
    elapsedInSeconds = curInSeconds - preInseconds;
}
else // rollover
{
    elapsedInSeconds = curInSeconds - preInseconds;
}

......这应该清楚表明,首先不需要处理翻转作为特殊情况。