Skip to content

Latest commit

 

History

History
90 lines (74 loc) · 2.82 KB

how-to-sign-extend.org

File metadata and controls

90 lines (74 loc) · 2.82 KB

符号扩展的实现方式

最近在看一些代码,里面要实现 “长度截断 + 符号扩展”,自带实现是下面这样的

#define SEXT(x, len) ({ struct { int64_t n : len; } __x = { .n = x }; (int64_t)__x.n; })

但是这个东西只对于编译期长度固定的情况有效,对于运行期则大约需要使用bit操作比如下面这样

#define SIGNEX(v, sb) ((v) | (((v) & ((uint64_t)1 << (sb))) ? ~(((uint64_t)1 << (sb)) - 1) : 0))

上面这种实现方式非常巧妙:

  • 前面一个部分负责 [0,sb-1] 这个范围的bits
  • 后面一个部分负责 [sb,63] 这个范围的bits

如果bits超过64也是可以的,因为 `((uint64_t)1 << sb)` 会回绕过来(或者是>>),这个非常有趣。我这里做了一个验证。

int main() {
    for (int i = 0; i < 32; i++) {
        uint64_t a = (uint64_t)1 << (i);
        uint64_t b = (uint64_t)1 << (i + 64);
        printf("a = 0x%llx, b = 0x%llx\n", a, b);
        assert(a == b);
    }
    for (int i = 0; i < 32; i++) {
        uint64_t a = (uint64_t)0x8000000000000000 >> (i);
        uint64_t b = (uint64_t)0x8000000000000000 >> (i + 64);
        printf("a = 0x%llx, b = 0x%llx\n", a, b);
        assert(a == b);
    }
}

我这里想了另外一个实现方式,思路就是完全使用指令本身的符号扩展功能:

  • 现将这个数左移到64位最高位
  • 然后算术右移回来,那么就自动实现了符号扩展
  • 在这个思路上也可以实现零扩展(zero-extended)
uint64_t signext(uint64_t value, int width) {
    int shift = (sizeof(uint64_t) - width) * 8;
    int64_t ans = ((int64_t)value << shift) >> shift;
    return ans;
}
uint64_t zeroext(uint64_t value, int width) {
    int shift = (sizeof(uint64_t) - width) * 8;
    return (value << shift) >> shift;
}
uint64_t zeroext2(uint64_t value, int width) {
    uint64_t mask = (1ULL << mask) - 1;
    return value & mask;
}

可以简单地验证下

int main() {
    struct Case {
        uint64_t value;
        int width;
        uint64_t exp;
    } cases[] = {
            {0x0ff, 1, (uint64_t)-1},
            {0x07f, 1, 0x07f},
            {0, 0, 0},
    };
    for (int i = 0; cases[i].value; i++) {
        uint64_t ans = signext(cases[i].value, cases[i].width);
        printf("case %d, ans = 0x%llx, exp = 0x%llx\n", i, ans, cases[i].exp);
        ans = signext(cases[i].value, cases[i].width + 64);
        printf("case %d, width + 64, ans = 0x%llx, exp = 0x%llx\n", i, ans, cases[i].exp);
        assert(ans == cases[i].exp);
        uint64_t zext = zeroext(cases[i].value, cases[i].width);
        assert(zext == cases[i].value);
        uint64_t zext2 = zeroext2(cases[i].value, cases[i].width);
        assert(zext2 == cases[i].value);

    }
}