符号扩展的实现方式

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

#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))

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

如果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);
    }
}


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

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);

    }
}