在 C 中,如何计算两个 48 位无符号整数之间的有符号差?

In C, How do I calculate the signed difference between two 48-bit unsigned integers?

我从一个无符号的 48 位纳秒计数器中得到了两个值,它们可能会回绕。

我需要两次的差值,以纳秒为单位。

我想我可以假设读数是在大致相同的时间进行的,所以在两个可能的答案中,我认为我选择最小的答案是安全的。

它们都存储为 uint64_t。因为我不认为我可以有 48 位类型。

我想计算它们之间的差值,作为一个带符号的整数(大概 int64_t),考虑到包装。

例如如果我从

开始
x=5

y=3

那么 x-y 的结果是 2,如果我同时增加 xy,即使它们覆盖在最大值 0xffffffffffff

类似地,如果 x=3,y=5,则 x-y 为 -2,并且每当 x 和 y 同时递增时都会保持不变。

如果我可以将 xy 声明为 uint48_t,并将差异声明为 int48_t,那么我认为

int48_t diff = x - y; 

就可以了。

如何使用可用的 64 位算法模拟此行为?

(我认为任何可能 运行 的计算机都会使用 2 的补码算法)

P.S。我可能可以解决这个问题,但我想知道是否有一个很好的标准方法来做这种事情,下一个阅读我的代码的人将能够理解。

P.P.S 此外,这段代码将以最紧密的循环结束,因此能够高效编译的东西会很好,所以如果必须选择,速度胜过可读性。

你知道哪个是早读哪个是晚读吗?如果是:

diff = (earlier <= later) ? later - earlier : WRAPVAL - earlier + later;

其中 WRAPVAL(1 << 48) 非常容易阅读。

您可以通过在任何算术运算后屏蔽掉 uint64_t 的前 16 位来模拟 48 位无符号整数类型。因此,例如,要计算这两个时间之间的差异,您可以这样做:

uint64_t diff = (after - before) & 0xffffffffffff;

即使计数器在过程中回转,您也会得到正确的值。如果计数器没有环绕,则不需要屏蔽但也无害。

现在,如果您希望编译器将此差异识别为有符号整数,则必须 sign extend 第 48 位。这意味着如果设置了第 48 位,则数字为负数,并且您要设置 64 位整数的第 49 位到第 64 位。我认为一个简单的方法是:

int64_t diff_signed = (int64_t)(diff << 16) >> 16;

警告: 您可能应该对此进行测试以确保它有效,并且还要注意当我将 uint64_t 转换为 [= 时存在实现定义的行为15=],并且我认为当我将带符号的负数向右移动时存在实现定义的行为。我相信 C 语言律师可以提出更强大的东西。

更新: OP 指出,如果将取差和符号扩展的操作结合起来,则不需要屏蔽。看起来像这样:

int64_t diff = (int64_t)(x - y) << 16 >> 16;
struct Nanosecond48{
    unsigned long long u48 : 48;
//  int res : 12; // just for clarity, don't need this one really
};

这里我们只使用字段的显式宽度为 48 位,并且使用这种(不可否认有点笨拙)类型,您可以让编译器正确处理不同的 architectures/platforms/whatnot。

喜欢以下内容:

Nanosecond48 u1, u2, overflow;
overflow.u48 = -1L;
u1.u48 = 3;
u2.u48 = 5;
const auto diff = (u2.u48 + (overflow.u48 + 1) - u1.u48) & 0x0000FFFFFFFFFFFF;

当然,在最后一条语句中,如果您愿意,您可以只用 % (overflow.u48 + 1) 进行余数运算。