为什么Java不支持无符号整数?

在我看来,这是一个奇怪的遗漏,因为它们允许人们编写不太可能在意外的大输入上产生溢出的代码。

此外,使用无符号整数可以是一种自我文档的形式,因为它们表明无符号整型所要保存的值永远不应该是负数。

最后,在某些情况下,无符号整数对于某些运算(如除法)更有效。

包含这些的缺点是什么?


http://skeletoncoder.blogspot.com/2006/09/java-tutorials-why-no-unsigned.html

这个家伙说,因为C标准定义了包含无符号整型和有符号整型的操作被视为无符号整型。这可能导致负符号整数滚动到一个大的无符号整数,可能会导致错误。


这是对高斯林和其他人的采访,关于简单:

Gosling: For me as a language designer, which I don't really count myself as these days, what "simple" really ended up meaning was could I expect J. Random Developer to hold the spec in his head. That definition says that, for instance, Java isn't -- and in fact a lot of these languages end up with a lot of corner cases, things that nobody really understands. Quiz any C developer about unsigned, and pretty soon you discover that almost no C developers actually understand what goes on with unsigned, what unsigned arithmetic is. Things like that made C complex. The language part of Java is, I think, pretty simple. The libraries you have to look up.


我听说它们将在接近最初的Java发行版时被包含。Oak是Java的前身,在一些规范文档中提到了使用值。不幸的是,这些都没有被引入Java语言。据任何人所知,可能由于时间限制,它们没有得到实施。


一旦有符号整型和无符号整型混合在表达式中,事情就开始变得混乱,你可能会丢失信息。将Java限制为有符号int型只能真正解决问题。我很高兴我不必担心整个有符号/无符号的问题,尽管我有时会错过字节中的第8位。


字里行间,我认为逻辑是这样的:

通常,Java设计人员希望简化可用的数据类型 对于日常用途,他们认为最常见的需求是有符号的数据类型 为了实现某些算法,有时需要无符号算术,但是要实现这种算法的程序员也应该具备使用有符号数据类型进行无符号算术的知识

总的来说,我认为这是一个合理的决定。我可能会:

使字节无符号,或者至少为这一数据类型提供了有符号/无符号的替代选项,可能使用不同的名称(使它有符号有利于一致性,但什么时候需要有符号字节?) 不再使用“short”(你上次使用16位符号算术是什么时候?)

不过,只要稍加修饰,对32位以内的无符号值进行运算就不会太糟糕,而且大多数人不需要无符号64位除法或比较。


我认为Java本身就很好,添加unsigned会使它变得复杂而没有太多好处。 即使使用简化的整数模型,大多数Java程序员也不知道基本的数字类型是如何行为的——只要阅读《Java Puzzlers》一书,就能了解您可能持有的误解。

至于实用的建议:

如果你的值是任意大小,不适合int,使用long。 如果它们不适合长期使用BigInteger。 只有在需要节省空间时,才对数组使用较小的类型。 如果你正好需要64/32/16/8位,使用long/int/short/byte,不要担心符号位,除法、比较、右移和强制转换除外。

另请参阅关于“将一个随机数生成器从C移植到Java”的回答。


我能想到一个不幸的副作用。在java嵌入式数据库中,使用32位id字段可以拥有的id数量是2^31,而不是2^32(~ 20亿,而不是~ 40亿)。


Java确实有unsigned类型,或者至少有一个:char是一个unsigned short类型。所以不管高斯林找什么借口,他都不知道为什么没有其他无符号类型。

还有短型:短型一直被用于多媒体。原因是您可以在一个32位无符号长函数中拟合2个样本,并向量化许多操作。8位数据和无符号字节也是如此。你可以在一个寄存器中放入4或8个样本进行向量化。


这是一个古老的问题,pat确实简单地提到了char,我只是想我应该为其他人扩展这个问题,他们将在未来的道路上看到这个问题。让我们仔细看看Java的基本类型:

字节- 8位有符号整数

短16位有符号整数

Int - 32位有符号整数

长64位有符号整数

Char - 16位字符(无符号整数)

虽然char不支持无符号算术,但它本质上可以被视为无符号整数。您必须显式地将算术运算转换回char类型,但它确实提供了一种指定无符号数字的方法。

char a = 0;
char b = 6;
a += 1;
a = (char) (a * b);
a = (char) (a + b);
a = (char) (a - 16);
b = (char) (b % 3);
b = (char) (b / a);
//a = -1; // Generates complier error, must be cast to char
System.out.println(a); // Prints ? 
System.out.println((int) a); // Prints 65532
System.out.println((short) a); // Prints -4
short c = -4;
System.out.println((int) c); // Prints -4, notice the difference with char
a *= 2;
a -= 6;
a /= 3;
a %= 7;
a++;
a--;

是的,没有对无符号整数的直接支持(显然,如果有直接支持,我就不必将大部分操作转换回char类型)。但是,肯定存在无符号基元数据类型。我也希望看到一个无符号字节,但我猜加倍内存成本,而不是使用char是一个可行的选择。


Edit

JDK8为Long和Integer提供了新的api,在将Long和int值作为无符号值处理时提供了辅助方法。

compareUnsigned divideUnsigned parseUnsignedInt parseUnsignedLong remainderUnsigned toUnsignedLong toUnsignedString

此外,Guava提供了许多帮助器方法来处理整数类型,这有助于弥补由于缺乏对无符号整数的本机支持而留下的空白。


恕我直言,原因是他们太懒了,没有去实施/纠正这个错误。 暗示C/ c++程序员不理解unsigned,结构,联合,位标志…太荒谬了。

如果你正在和一个基本的/bash/java程序员交谈,即将开始用C语言编程,没有任何真正的语言知识,或者你只是在说你自己的想法。;)

当你每天处理文件或硬件的格式时,你会开始质疑,他们到底在想什么。

一个很好的例子是尝试使用无符号字节作为自旋转循环。 对于那些不理解最后一句话的人,你究竟是如何称自己为程序员的。

DC


在JDK8中,它确实提供了一些支持。

尽管有Gosling的担忧,但我们仍然可能看到Java对unsigned类型的完全支持。


我知道这个帖子太老了;但是,在Java 8及以后版本中,您可以使用int数据类型来表示无符号32位整数,其最小值为0,最大值为232−1。使用Integer类使用int数据类型作为无符号整数,并且像compareUnsigned(), divideUnsigned()等静态方法已经添加到Integer类中,以支持无符号整数的算术操作。


因为无符号类型是纯粹的邪恶。

事实上,在C语言中unsigned int生成unsigned更是邪恶的。

下面是一个让我不止一次头疼的问题的快照:

// We have odd positive number of rays, 
// consecutive ones at angle delta from each other.
assert( rays.size() > 0 && rays.size() % 2 == 1 );

// Get a set of ray at delta angle between them.
for( size_t n = 0; n < rays.size(); ++n )
{
    // Compute the angle between nth ray and the middle one.
    // The index of the middle one is (rays.size() - 1) / 2,
    // the rays are evenly spaced at angle delta, therefore
    // the magnitude of the angle between nth ray and the 
    // middle one is: 
    double angle = delta * fabs( n - (rays.size() - 1) / 2 ); 

    // Do something else ...
}

你注意到这个bug了吗?我承认我是在使用调试器之后才看到它的。

由于n是无符号类型size_t,整个表达式n - (ray .size() - 1) / 2的计算结果为无符号。该表达式旨在表示从中间那条线开始的第n条射线的符号位置:从左边那条线开始的第1条射线的位置为-1,右边那条线的位置为+1,等等。在取abs值并乘以角之后,我将得到第n条射线与中间那条射线之间的夹角。

不幸的是,对我来说,上面的表达式包含了邪恶的unsigned,它的计算结果不是-1,而是2^32-1。随后转换为双密封的bug。

由于滥用无符号算术而导致的一两个错误之后,人们不得不开始考虑获得的额外比特是否值得额外的麻烦。我正在尽可能地避免在算术中使用无符号类型,尽管仍然将它用于非算术操作,如二进制掩码。


I once took a C++ course with someone on the C++ standards committee who implied that Java made the right decision to avoid having unsigned integers because (1) most programs that use unsigned integers can do just as well with signed integers and this is more natural in terms of how people think, and (2) using unsigned integers results in lots easy to create but difficult to debug issues such as integer arithmetic overflow and losing significant bits when converting between signed and unsigned types. If you mistakenly subtract 1 from 0 using signed integers it often more quickly causes your program to crash and makes it easier to find the bug than if it wraps around to 2^32 - 1, and compilers and static analysis tools and runtime checks have to assume you know what you're doing since you chose to use unsigned arithmetic. Also, negative numbers like -1 can often represent something useful, like a field being ignored/defaulted/unset while if you were using unsigned you'd have to reserve a special value like 2^32 - 1 or something similar.

Long ago, when memory was limited and processors did not automatically operate on 64 bits at once, every bit counted a lot more, so having signed vs unsigned bytes or shorts actually mattered a lot more often and was obviously the right design decision. Today just using a signed int is more than sufficient in almost all regular programming cases, and if your program really needs to use values bigger than 2^31 - 1, you often just want a long anyway. Once you're into the territory of using longs, it's even harder to come up with a reason why you really can't get by with 2^63 - 1 positive integers. Whenever we go to 128 bit processors it'll be even less of an issue.


你的问题是“为什么Java不支持无符号整数”?

我对你的问题的回答是,Java希望它所有的基本类型:byte, char, short, int和long应该分别被视为字节,word, dword和qword,就像在汇编中一样,Java操作符对所有的基本类型都是有符号的操作,除了char,但只有在char上它们只能是无符号的16位。

因此静态方法对于32位和64位也是无符号操作。

您需要final类,它的静态方法可以用于无符号操作。

你可以创建这个最终类,给它起任何你想要的名字,并实现它的静态方法。

如果你不知道如何实现静态方法,那么这个链接可以帮助你。

在我看来,如果Java既不支持无符号类型,也不支持操作符重载,那么它一点也不类似于c++,所以我认为Java应该被视为与c++和C完全不同的语言。

顺便说一下,这两种语言的名称也完全不同。

所以我不建议在Java中输入类似C的代码也不建议输入类似c++的代码,因为在Java中,你将无法在c++中做你想做的事情,也就是说,代码将不会继续像c++那样,对我来说,这样的代码很糟糕,在中间改变风格。

我建议也为有符号操作编写和使用静态方法,这样就不会在代码中看到有符号和无符号操作混合使用操作符和静态方法,除非在代码中只需要有符号操作,而且只使用操作符是可以的。

此外,我建议避免使用short、int和long基元类型,分别使用word、dword和qword,并且您将调用无符号操作和/或有符号操作的静态方法,而不是使用操作符。

如果您打算只执行有符号的操作,并且只在代码中使用操作符,那么使用这些原语类型short、int和long是可以的。

实际上word, dword和qword在语言中不存在,但你可以为它们创建新类,并且它们的实现应该非常容易:

The class word holds the primitive type short only, the class dword holds the primitive type int only and the class qword holds the primitive type long only. Now all the unsigned and the signed methods as static or not as your choice, you can implement in each class, i.e. all the 16 bit operations both unsigned and signed by giving meaning names on the word class, all the 32 bit operations both unsigned and signed by giving meaning names on the dword class and all the 64 bit operations both unsigned and signed by giving meaning names on the qword class.

如果你不喜欢给每个方法取太多不同的名字,你可以在Java中使用重载,很高兴看到Java没有删除它!

如果您想要8位有符号操作的方法而不是操作符,或者想要8位无符号操作的方法而根本没有操作符,那么您可以创建Byte类(注意,第一个字母'B'是大写的,所以这不是基本类型Byte)并在该类中实现方法。

关于按值传递和按引用传递:

If I am not wrong, like in C#, primitive objects are passed by value naturally, but class objects are passed by reference naturally, so that means that objects of type Byte, word, dword and qword will be passed by reference and not by value by default. I wish Java had struct objects as C# has, so all Byte, word, dword and qword could be implemented to be struct instead of class, so by default they were passed by value and not by reference by default, like any struct object in C#, like the primitive types, are passed by value and not by reference by default, but because that Java is worse than C# and we have to deal with that, then there is only classes and interfaces, that are passed by reference and not by value by default. So if you want to pass Byte, word, dword and qword objects by value and not by reference, like any other class object in Java and also in C#, you will have to simply use the copy constructor and that's it.

这是我能想到的唯一解决办法。我只是希望我可以将原始类型类型定义为word, dword和qword,但Java既不支持typedef,也不支持using,不像c#支持using,这相当于C的typedef。

输出:

对于相同的比特序列,可以以多种方式打印它们:二进制、十进制(就像C printf中%u的含义)、八进制(就像C printf中%o的含义)、十六进制(就像C printf中%x的含义)和整数(就像C printf中%d的含义)。

请注意,C printf不知道作为参数传递给函数的变量的类型,因此printf只知道传递给函数第一个形参的char*对象中的每个变量的类型。

所以在每一个类:Byte, word, dword和qword中,你可以实现print方法并获得printf的功能,即使类的基本类型是有符号的,你仍然可以通过遵循一些涉及逻辑和移位操作的算法将其作为无符号打印到输出中。

不幸的是,我给你的链接没有显示如何实现这些打印方法,但我相信你可以谷歌的算法,你需要实现这些打印方法。

这就是我所能回答你的问题并建议你的。


在“C”规范中,有一些因实用主义原因而被Java抛弃的珍宝,但随着开发人员的需求(闭包等),它们正在慢慢地回归。

我提到第一个是因为它和这个讨论有关;指针值对无符号整数算术的坚持。并且,与这个主题相关的是,在Java的Signed世界中维护Unsigned语义的困难。

我猜如果有人让Dennis Ritchie的另一个自我来建议Gosling的设计团队,他会建议给Signed's一个“无穷大的零”,这样所有的地址偏移请求都会先加上他们的algeaic RING SIZE来消除负值。

这样,向数组抛出的任何偏移量都不会生成SEGFAULT。例如,在一个封装类中,我称之为RingArray的双精度对象需要unsigned行为-在“自旋转循环”上下文中:

// ...
// Housekeeping state variable
long entrycount;     // A sequence number
int cycle;           // Number of loops cycled
int size;            // Active size of the array because size<modulus during cycle 0
int modulus;         // Maximal size of the array

// Ring state variables
private int head;   // The 'head' of the Ring
private int tail;   // The ring iterator 'cursor'
// tail may get the current cursor position
// and head gets the old tail value
// there are other semantic variations possible

// The Array state variable
double [] darray;    // The array of doubles

// somewhere in constructor
public RingArray(int modulus) {
    super();
    this.modulus = modulus;
    tail =  head =  cycle = 0;
    darray = new double[modulus];
// ...
}
// ...
double getElementAt(int offset){
    return darray[(tail+modulus+offset%modulus)%modulus];
}
//  remember, the above is treating steady-state where size==modulus
// ...

上面的RingArray永远不会从负索引中“获得”,即使恶意请求者试图这样做。记住,还有许多合法的请求用于请求先前的(负的)索引值。

注意:外层%模数去掉了对合法请求的引用,而内部%模数掩盖了明显的恶意,因为负数比-模数更负。如果这将出现在Java +..9 || 8+…+ spec,那么问题将真正成为一个“程序员不能“自我旋转”的错误”。

我相信所谓的Java unsigned int“缺陷”可以用上面的一行程序来弥补。

PS:只是为了给上面的RingArray管理提供上下文,这里有一个候选的'set'操作来匹配上面的'get'元素操作:

void addElement(long entrycount,double value){ // to be called only by the keeper of entrycount
    this.entrycount= entrycount;
    cycle = (int)entrycount/modulus;
    if(cycle==0){                       // start-up is when the ring is being populated the first time around
        size = (int)entrycount;         // during start-up, size is less than modulus so use modulo size arithmetic
        tail = (int)entrycount%size;    //  during start-up
    }
    else {
        size = modulus;
        head = tail;
        tail = (int)entrycount%modulus; //  after start-up
    }
    darray[head] = value;               //  always overwrite old tail
}

作为处理过无符号算术的人,我可以向您保证,在Java中确实没有必要使用无符号数字。

以C语言为例。让我们这样写:

unsigned int num = -7;
printf("%d", num);

你能猜到上面印的是什么吗?

-7

哇!无符号整数是负的!完全正确。没有真正的正整数。无符号整数只是一个n字节(取决于C语言中的体系结构)的值,它不为符号分配MSB。它不检查分配或读取的数字的实际符号。