Java无符号除法无需强制转换为long?

发布于 2024-08-14 21:31:08 字数 334 浏览 6 评论 0原文

我编写了一个解释器,要求我执行无符号整数的 32 位除法。在 Java 中,我可以这样做:

reg[a] = (int) ((reg[b] & 0xFFFFFFFFL) / (reg[c] & 0xFFFFFFFFL));

但我想避免转换为 long 并返回 int。 Java 已经为这种特殊情况提供了无符号右移运算符 >>>,因此也许有一种聪明的方法可以以相同的方式进行无符号除法。

请注意,加法和乘法可以正常工作,因为二进制的补数可以正常工作。

Java 有没有更好的方法来做到这一点?

I have written an interpreter that requires me to perform 32-bit division of unsigned integers. In Java, I can do this as:

reg[a] = (int) ((reg[b] & 0xFFFFFFFFL) / (reg[c] & 0xFFFFFFFFL));

But I would like to avoid the conversion to long and back to int. Java already gives the unsigned right shift operator >>> for that special case, so maybe there is a clever way to do unsigned division in the same way.

Note that add and multiply work fine, since two's compliment numbers just work.

Is there a better way in Java to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

总攻大人 2024-08-21 21:31:08

在 Java 8 及更高版本中,Integer 具有对无符号 int 进行操作的完整集合

reg[a] = Integer.divideUnsigned(reg[b], reg[c]);

In Java 8 and up, Integer has a whole collection of operations on unsigned ints

reg[a] = Integer.divideUnsigned(reg[b], reg[c]);
如果没结果 2024-08-21 21:31:08

那么,如果向下移动一位,则可以将所得的两个数字相除,然后向上移动两次(因为所得的数字将小 4 倍)。但这仅适用于偶数,因为您会丢失最低有效位。

我真的不认为这会节省你检查这种情况的时间。 (或检查小于 231 的数字)

Well, if you shift down by one bit, you could divide the resulting two numbers, then shift up twice (because the resulting number would be 4 times smaller). But that would only work on even numbers, since you would lose the least significant bit.

I don't really think it would save you any time to check for that condition. (or check for numbers smaller then 231)

沉默的熊 2024-08-21 21:31:08

您始终可以使用 BigInteger,它适用于任意大小的整数,但这比提升为 long 并强制转换为 int 的成本要高得多>。您的目的是提高性能(因此您需要一个“纯整数”解决方案来避免转换时间)还是提高代码的可读性/可理解性(在这种情况下 BigInteger 可能更整洁)?

You could always used BigInteger, which works on arbitrary sized integers, but that would be a lot more expensive than promoting to long and cast back as int. Is your intention to improve performance (hence you want a "pure integer" solution to avoid the time for casts) or to improve how readable/understandable the code is (in which case BigInteger might be neater)?

土豪 2024-08-21 21:31:08

其他人提到了简单且明确正确的方法,例如转换为 long 或使用 Integer.divideUnsigned() (Java SE 8+),或使用 <代码>BigInteger

实际上,Integer.divideUnsigned() 是最清晰、最高效的方法,因为 JVM 可能将此函数调用内化为本机无符号除法机器指令,从而使得它的运行速度与语言级有符号除法运算符 / 一样快。

但为了完整起见,以下是如何以相对有效的方式在纯 Java(无 C 或汇编)中模拟 uint32 / uint32不使用更广泛的类型,如 < code>long 或 BigInteger

int divideUint32(int x, int y) {
    if (y < 0) {  // i.e. 2^31 <= unsigned y < 2^32
        // Do unsigned comparison
        return (x ^ 0x8000_0000) >= (y ^ 0x8000_0000) ? 1 : 0;
    }
    else if (x >= 0 || y == 0) {
        assert y >= 0;
        return x / y;  // Straightforward or division by zero
    }
    else {  // The hard case: signed x < 0 && signed y > 0
        assert x < 0 && y > 0;
        // In other words, 2^31 <= unsigned x < 2^32 && 0 < unsigned y < 2^31
        int shift = Integer.numberOfLeadingZeros(y);
        assert shift > 0;
        assert (y << shift) < 0;
        // Do one step of long division
        if (x < (y << shift))
            shift--;
        return (1 << shift) | (x - (y << shift)) / y;
    }
}

我已经在整个 int 值范围内对数百万个随机测试用例检查了此代码。如果有人想知道如何调整代码以使用 long,通过遵循逻辑中的隐式模式很容易做到这一点。

Other people have mentioned simple and clearly correct approaches like casting to long, or using Integer.divideUnsigned() (Java SE 8+), or using BigInteger.

Practically speaking, Integer.divideUnsigned() is the clearest and most performant way to do it, because the JVM probably intrinsifies this function call into a native unsigned division machine instruction, making it run as fast as the language-level signed division operator /.

But for the sake of completeness, here is how to emulate uint32 / uint32 in pure Java (no C or assembly) in a relatively efficient manner, without using wider types like long or BigInteger:

int divideUint32(int x, int y) {
    if (y < 0) {  // i.e. 2^31 <= unsigned y < 2^32
        // Do unsigned comparison
        return (x ^ 0x8000_0000) >= (y ^ 0x8000_0000) ? 1 : 0;
    }
    else if (x >= 0 || y == 0) {
        assert y >= 0;
        return x / y;  // Straightforward or division by zero
    }
    else {  // The hard case: signed x < 0 && signed y > 0
        assert x < 0 && y > 0;
        // In other words, 2^31 <= unsigned x < 2^32 && 0 < unsigned y < 2^31
        int shift = Integer.numberOfLeadingZeros(y);
        assert shift > 0;
        assert (y << shift) < 0;
        // Do one step of long division
        if (x < (y << shift))
            shift--;
        return (1 << shift) | (x - (y << shift)) / y;
    }
}

I have checked this code on millions of random test cases over the full range of int values. In case anyone is wondering about adapting the code to work with long, it is easy to do so by following the implicit patterns in the logic.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文