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;
}
答案 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 << 1
或2*b
。
然后采用按位补码,
~(b << 1)
给出ULONG_MAX - (b << 1)
。
如果b == 0
,则向ULONG_MAX - (b << 1)
添加1会导致0,否则
~(b << 1) + 1
给出
2^w - (b << 1)
如果b == 0
和
2^(w-1) - b
否则。因此
preInseconds = ((~(preInseconds <<1)+1)>>1);
将preInseconds
设置为
2^(w-1) - b
如果b != 0
,则{b == 0
。
最后,
elapsedInSeconds = curInSeconds + preInseconds;
因此,如果elapsedInSeconds
为curInSeconds
[如果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;
}
......这应该清楚表明,首先不需要处理翻转作为特殊情况。