【问题标题】:In C, How do I calculate the signed difference between two 48-bit unsigned integers?在 C 中,如何计算两个 48 位无符号整数之间的有符号差?
【发布时间】:2017-10-25 06:57:29
【问题描述】:

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

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

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

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

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

例如如果我从

开始
x=5

y=3

那么x-y 的结果是2,如果我同时增加xy,即使它们超过最大值0xffffffffffff 的顶部,也会保持不变

同样,如果 x=3,y=5,则 x-y 为 -2,并且只要 x 和 y 同时递增,就会保持这种状态。

如果我可以将x,y 声明为uint48_t,而区别为int48_t,那么我想

int48_t diff = x - y; 

会起作用的。

如何使用现有的 64 位算法模拟这种行为?

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

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

P.P.S 另外,这段代码最终会出现在最紧凑的循环中,所以能够高效编译的东西会很好,所以如果必须有选择,速度胜过可读性。

【问题讨论】:

  • 我已经很久没有处理 2 的补码了,但我认为如果你写 uint64_t diff = (x - y) & 0x0000 ffff ffff ffff 那么答案将是正确的,就像你有 48 位整数一样。
  • 这看起来非常合理,读起来也很好。但它总是会有 0 个最高位,那么它怎么可能是一个 -ve 值作为 int64_t 呢?
  • 它不会是负数int64_t,但是当设置最高位时,它将是负数“int48_t”。 -1 是 0xff...,所以 "int48_t" 中的 -1 将表示为 0x0000ff... 如果您想说x < 0 之类的内容,这将很棘手,必须将其重写为x <= (((int64_t)-1) & 0x0000ff...) .
  • 当然,所以现在我需要将其转换为 int64_t
  • 为了澄清这个问题——x 可能在 y 之前,也可能 x 在 y 之后,而您想要一个否定的结果来表示后者? (而不是知道 x 是初始测量值,y 是最终测量值,结果应该是无符号的)。

标签: c math unsigned signed


【解决方案1】:

您可以通过在任何算术运算后仅屏蔽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 转换为 int64_t 时,还要注意实现定义的行为,我认为有当我将带符号的负数向右移动时,实现定义的行为。我敢肯定 C 语言律师可以提出一些更强大的东西。

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

int64_t diff = (int64_t)(x - y) << 16 >> 16;

【讨论】:

  • 哦,看起来不错,让我考虑一下(测试正在建设中......)
  • 我刚想出的另一个想法是在对它们进行任何操作之前将 48 位无符号整数向左移动 16,这样你的读数都是 uint64_t 数字并且它们以预期的形式换行位置,并且您不必右移任何有符号的数字。
  • 原始答案不起作用,我认为我们需要算术右移,但上面的代码正在做一个合乎逻辑的操作(gcc,我认为行为通常是未定义的?)。不过这个新想法看起来很合理!
  • 糟糕,抱歉,它在 gcc 中确实有效,我在测试中得到了一些 0xffffffffff。但我认为它只有效,因为 gcc 做了理智的事情。不过应该够用了吧!我们非常喜欢 gcc,任何其他会在有符号值上进行逻辑右移的编译器都可能会爬掉并死掉。
  • 经过一番沸腾和测试: return ((int64_t)((x-y)>16);返回 ((int64_t)((x>16;两者都工作得很好。在任何一种情况下,您都不需要面具。您想添加一个或两个作为答案吗?
【解决方案2】:
struct Nanosecond48{
    unsigned long long u48 : 48;
//  int res : 12; // just for clarity, don't need this one really
};

在这里,我们只是将字段的显式宽度设为 48 位,并且使用这种(诚然有些尴尬)类型,您可以让编译器正确处理不同的架构/平台/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) 进行余数运算。

【讨论】:

  • 注意,位域是否可以大于unsigned int
【解决方案3】:

你知道哪个是较早的阅读,哪个是较晚的阅读吗?如果是这样:

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

WRAPVAL(1 &lt;&lt; 48) 的位置很容易阅读。

【讨论】:

  • 不,但我认为我可以假设读数相当接近,所以在两个可能的答案中,我认为我可以选择最小的一个。编辑问题以澄清。
  • @JohnLawrenceAspden 如果计数器包含在两个测量值之间,则取最小值将不起作用,并且似乎问题的措辞是说您希望代码能够处理包含的计数器在两次测量之间
  • 绝对值最小。如果这两个测量值彼此接近,那么一个可能的差异将是巨大的并且可以忽略。
猜你喜欢
  • 2012-02-11
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 2012-09-18
  • 2016-08-17
  • 2017-09-12
  • 1970-01-01
  • 2012-08-30
相关资源
最近更新 更多