快速舍入定点数

时间:2021-05-07 23:28:29

标签: c++

假设我将整数视为有 4 个小数位。现在 0 是 0,16 是 1,32 是 2,然后继续下去。舍入时,[-7, 7] 范围内的数字变为 0,[8, 23] 变为 16。

我的代码是这样的:

std::int64_t my_round(std::int64_t n) {
    auto q = n / 16;
    auto r = n % 16;
    if (r >= 0) {
        if (r >= 8) {
            ++q;
        }
    } else {
        if (r <= -8) {
            --q;
        }
    }
    return q * 16;
}

对于这样一个简单的任务,需要大量的代码。我想知道是否有更快的方法来做到这一点。我只需要支持 64 位有符号整数。

编辑: 有一条评论(我不记得是谁发表的评论)建议添加 15 并屏蔽较低的位。它没有用。但是经过反复试验,我想出了这个。

std::int64_t my_round2(std::int64_t n) {
    if (n >= 0) {
        n += 8;
    }
    else {
        n += 7;
    }
  return n & (~15ll);
}

我不知道。但 my_round2 似乎给出与 my_round 相同的结果,而且速度要快 20 倍。如果有办法去掉分支就更好了。

3 个答案:

答案 0 :(得分:4)

return (n + 8 + (n>>63)) & (~15ll);

可以将my_round2()的分支剃掉,并确保原始对称性为零。这个想法是 signed type >> (sizeof(signed type) * 8 - 1) 对于负值是 -1,对于正值是 0。

Clang 能够为原始 my_round2() 生成无分支代码,但这仍然比此处建议的例程长一条指令。使用 arm64 节省更多。

答案 1 :(得分:1)

类似的东西怎么样

std::int64_t my_round2(std::int64_t n) {
    int sign = n >= 0 ? 1 : -1;
    n = sign > 0 ? n: -n;
    int64_t n1 = (n + 8) >>4;
    n1<<= 4;
    return n1 * sign;
}

这仍然存在溢出问题。

答案 2 :(得分:1)

只要您的整数表示是二进制补码(C11 标准中的其他东西几乎都需要),只需添加 8 并屏蔽低位:

int64_t my_round2(int64_t n) {
    return (n + 8) & ~UINT64_C(15);
}