处理 Java 8 中 "unsigned" 整数的想法

Ideas to deal with "unsigned" integers in Java 8

我正在编写与硬件和数学相关的代码,并广泛使用 32 位和 64 位无符号整数值。在没有任何 javac 支持的情况下,错误一直很稳定且难以解决。 Java 8 在 boxed Long class 上添加了一些函数用于除法、mod 和比较,但是虽然这些提供了 运行-time 支持,但缺少编译时关于两者意外混合的警告让我想拔头发。

有人知道有什么方法可以帮助解决这些问题吗? Java 团队成员之一提到 possible annotation support 进行类型检查。

到目前为止,我在所有无符号变量前加上 u_,我什至尝试在每次出现应无符号的 intlong 之前添加一个 /* unsigned */ 注释。虽然这些很有用,但它们也非常混乱。这还不够。犯错的机会实在是太多了。

两个较大的问题是不需要的符号扩展和混合运算。

/* unsigned */ long u_lowmask = 0xffffffff 这样无伤大雅的东西并没有达到预期的结果,甚至只是 /* unsigned */ long u_widen64 = small32 就毁了那个无声扩展的东西。在其他语言中,我可以从编译器或类似 Lint 的静态检查器获得警告。函数式语言通常将其构建到类型检查机制中,但 Java 避开了这些,转而采用其他解决方案。混合比较或操作的机会太多了。

任何想法都应该没有 运行 时间影响,所以我无法承受将 unsigned 包装在 class 或 bignums 中。好吧,如果我可以在没有任何分配或线程本地开销的情况下完成它,我也许可以将它们包装在 class 中,但这可能会使代码不可重入。此外,Hotspot 还必须能够为接近简单本机的包装无符号整数发出代码。 (实际上,我认为 Hotspot 可能足够聪明,可以接近 - 可能是额外的内存读写。)

其他人如何围绕这个问题进行编码?

在检测端,我没有研究太多,但我会检查一下Lint4j or other Java lint tools from this answer

理想情况下,该工具会在每次将 long 变量分配给 int 值时向您发出警告,然后您可以使用如下解决方案将每个此类分配包装在静态(可内联)方法调用(从而删除警告)。

对于赋值静态包装器方法,像这样的实用程序 class 过去对我很有效:

public class Convert {
    private Convert() {} // static utility methods only

    public long uintToUlong(int uint) {
        long ulong = 0;
        if (uint < 0) {
            uint &= Integer.MAX_VALUE;
            ulong = Integer.MAX_VALUE;
            ulong++;
        }
        return ulong + uint;
    }

    public long uintToLong(int uint) {
        return uintToUlong(uint)
    }

    public long intToUlong(int signedInt) {
        return signedInt; //TODO would this be correct?
    }

    public long intToLong(int signedInt) {
        return signedInt;
    }
}

或者这种样式在您的代码中可能更具可读性:

public class Uint {
    /** this class has only static utility methods and is not to be instantiated */
    private Uint() {
        super();
    }

    public static long toUlong(int uint) {
        return Convert.uintToUlong(uint);
    }
}

一些 JUnit 测试:

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class UintTest {
    @Test
    public void testToUlong() {
        long expected = Double.valueOf(Math.pow(2, 32) - 1).longValue();
        assertEquals("maxUint", expected, Uint.toUlong(-1));
        expected = Double.valueOf(Math.pow(2, 31)).longValue();
        assertEquals("minInt", expected, Uint.toUlong(Integer.MIN_VALUE));
        expected--;
        assertEquals("maxInt", expected, Uint.toUlong(Integer.MAX_VALUE));
        expected = 10000;
        assertEquals("10000", 10000l, Uint.toUlong(10000));
        expected = 3000000000l;
        assertEquals("3B", expected, Uint.toUlong(1500000000 + 1500000000));
    }
}

编辑:截至 2016 年 7 月,Checker Framework 附带了您所要求的大部分或全部内容:Signedness Checker, which verifies consistent use of signed vs. unsigned values. From the manual:

The Signedness Checker guarantees that signed and unsigned values are not mixed together in a computation. In addition, it prohibits meaningless operations, such as division on an unsigned value.

(向@MAGx2 致敬,因为他注意到这个答案已经过时了。)


旧答案如下:

我建议您查看 Checker Framework。它使您能够定义自己的类型限定符注释,例如 @Unsigned,然后在编译时检查您的代码相对于这些注释的类型是否正确。如果它没有发出警告,那么您可以保证您的代码不会混合有符号和无符号值。

Checker Framework 附带 20 type-checkers, but not one for unsigned arithmetic. You would need to write your own type-checker。这应该相对简单,因为类型检查器需要一些特殊规则:您只是不想混合有符号和无符号值。

有关需要库注释的 JDK 方法的列表,请参阅 http://hg.openjdk.java.net/jdk8/tl/jdk/rev/71200c517524