Java为什么不包括对无符号整数的支持?
在我看来,这是一个奇怪的遗漏,因为它们允许人们编写不太可能在意外大的输入上产生溢出的代码。
此外,使用无符号整数可以是一种自我证明的形式,因为它们指示无符号int打算保留的值永远不会为负。
最后,在某些情况下,无符号整数对于某些操作(例如除法)可能更有效。
包括这些的不利之处是什么?
byte
不能给出纯正的140
灰度级,而是-116
需要& 0xff
获得正确的值。
Java为什么不包括对无符号整数的支持?
在我看来,这是一个奇怪的遗漏,因为它们允许人们编写不太可能在意外大的输入上产生溢出的代码。
此外,使用无符号整数可以是一种自我证明的形式,因为它们指示无符号int打算保留的值永远不会为负。
最后,在某些情况下,无符号整数对于某些操作(例如除法)可能更有效。
包括这些的不利之处是什么?
byte
不能给出纯正的140
灰度级,而是-116
需要& 0xff
获得正确的值。
Answers:
这是来自对Gosling和其他人的关于简单性的采访:
高斯林:对于我作为语言设计师来说,这几天我并不算是真正的自己,“简单”最终的含义是我可以期望J. Random Developer掌握该规范。该定义表明,例如,Java并非如此-实际上,其中许多语言最终都带有很多极端情况,这是没人真正理解的。询问任何C开发人员有关无符号的知识,很快您就会发现几乎没有C开发人员真正了解无符号的含义,无符号的算法是什么。这样的事情使C变得复杂。我认为Java的语言部分非常简单。您必须查找的库。
在两行之间阅读,我认为逻辑是这样的:
通常,我会说这是一个合理的决定。可能,我会:
尽管如此,通过一点点合并,对不超过32位的无符号值的操作也不错,而且大多数人不需要进行无符号的64位除法或比较。
short
它的使用频率-defltate / gzip / inflate算法为16位,并且它们严重依赖于短裤...或者至少short[]
[诚然,它们是本机的-但是该算法的java impl携带terrabytes的数据]。后者(short[]
)具有显着的优势,int[]
因为它占用的内存减少了两倍,而内存减少了=更好的缓存属性,性能大大提高。
这是一个比较老的问题,帕特确实提到了char,我只是认为我应该为其他会在以后研究这个问题的人扩展这一点。让我们仔细看看Java基本类型:
byte
-8位有符号整数
short
-16位有符号整数
int
-32位有符号整数
long
-64位有符号整数
char
-16位字符(无符号整数)
尽管char
不支持unsigned
算术运算,但实际上可以将其视为unsigned
整数。您必须将算术运算明确地转换回char
,但是它确实为您提供了一种指定unsigned
数字的方法。
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是一个可行的选择。
借助JDK8,有新的API Long
,Integer
它们在将long
和int
值视为无符号值时提供了辅助方法。
compareUnsigned
divideUnsigned
parseUnsignedInt
parseUnsignedLong
remainderUnsigned
toUnsignedLong
toUnsignedString
此外,Guava提供了许多辅助方法来对整数类型执行类似的操作,这有助于弥补由于缺乏对unsigned
整数的本机支持而留下的空白。
Java确实有无符号类型,或者至少有一种:char是无符号的short。因此,不管高斯林抛出什么借口,实际上就是他的无知,为什么没有其他未签名的类型。
还有短裤类型:短裤一直用于多媒体。原因是您可以在一个32位无符号长中容纳2个样本,并对多个操作进行向量化。8位数据和无符号字节相同。您可以将4或8个样本放入寄存器中进行矢量化。
char
除字符外,其他任何样式都不好用。
一旦在表达式中混合了有符号和无符号的整数,事情就会开始变得混乱,您可能会丢失信息。将Java限制为带符号的整数只能真正清除问题。我很高兴我不必担心整个已签名/未签名的业务,尽管有时我确实会错过字节中的第8位。
static_cast
周围撒很多s来混合它们。确实是一团糟。
byte
像在Pascal中那样对Java 进行签名。
& 0xFF
每次字节到整数的提升都会使代码更加混乱。
http://skeletoncoder.blogspot.com/2006/09/java-tutorials-why-no-unsigned.html
这个家伙说,因为C标准将涉及无符号和有符号的int的操作定义为无符号的。这可能会导致带负号的整数转为一个大的无符号int,从而可能导致错误。
-1
与任何无符号量-甚至零进行比较都会遇到问题。
-1
“未知”年龄(如文章所示)是“代码气味”的经典示例之一。例如,如果您要计算“爱丽丝比鲍勃大多少岁?”,并且A = 25和B = -1,您将得到答案,±26
那是完全错误的。对未知值的正确处理是Option<TArg>
何时Some(25) - None
返回的某种方式None
。
我认为Java很好,添加unsigned会使它变得复杂,而没有太多收益。即使使用简化的整数模型,大多数Java程序员也不知道基本数字类型的行为-只需阅读《Java Puzzlers》一书,看看您可能会遇到什么误解。
至于实用建议:
如果您的值是任意大小且不合适int
,请使用long
。如果它们不适合long
使用BigInteger
。
需要节省空间时,仅将较小的类型用于数组。
如果您恰好需要64/32/16/8位,请使用long
/ int
/ short
/ byte
并停止担心符号位,除除,比较,右移和强制转换外。
又见这个回答有关“移植一个随机数发生器从C到Java的”。
>>
,并>>>
分别签署和无符号。向左移动没有问题。
>>>
不适用于short
和byte
。例如,(byte)0xff>>>1
收益0x7fffffff
而不是0x7f
。另一个示例:byte b=(byte)0xff; b>>>=1;
将产生b==(byte)0xff
。当然可以,b=(byte)(b & 0xff >> 1);
但这又增加了一个操作(按位&)。
我知道这个帖子太旧了;但是,出于您的兴趣,在Java 8和更高版本中,可以使用int
数据类型表示无符号32位整数,其最小值为0,最大值为2 32 -1。使用Integer
类使用int
的数据类型为无符号整数和静态方法一样compareUnsigned()
,divideUnsigned()
等已经加入到Integer
类,以支持算术运算的无符号整数。
我曾经与C ++标准委员会的某人一起上过C ++课程,他暗示Java做出了避免使用无符号整数的正确决定,因为(1)大多数使用无符号整数的程序都可以对有符号整数做同样的事情,这在自然情况下更为自然。人们的想法,以及(2)使用无符号整数会导致很多容易创建但难以调试的问题,例如整数有符号溢出和在有符号和无符号类型之间进行转换时会丢失大量位。如果您错误地使用带符号整数从0中减去1,则通常比包裹到2 ^ 32-1更快,它会使程序崩溃并更容易发现错误,并且编译器和静态分析工具以及运行时检查必须假设您选择使用无符号算术,就知道自己在做什么。也,
很久以前,当内存有限并且处理器不能一次自动在64位上运行时,每个位的计数都增加了很多,因此,有符号与无符号字节或短路的关系实际上更为重要,这显然是正确的设计决策。今天,几乎在所有常规编程情况下,仅使用带符号的int绰绰有余,并且如果您的程序确实需要使用大于2 ^ 31-1的值,那么无论如何,您通常只想要很长的时间。一旦您进入使用多头的领域,就很难想出为什么您真的无法使用2 ^ 63-1个正整数的原因。每当我们使用128位处理器时,问题就更少了。
您的问题是“为什么Java不支持unsigned ints”?
我对您的问题的回答是Java希望将其所有原始类型:byte,char,short,int和long分别对待为byte,word,dword和qword,就像在汇编中一样,并且对Java操作符进行签名除char以外,对所有其原始类型进行的操作,但仅在char上,它们仅是无符号的16位。
因此,静态方法假定对于32位和64位也是无符号操作。
您需要最终类,可以为未签名操作调用其静态方法。
您可以创建该最终类,将其命名为所需的任何名称,并实现它的静态方法。
如果您不知道如何实现静态方法,那么此链接可能会对您有所帮助。
在我看来,Java是不是类似于C ++ 在所有的,如果它既不支持无符号的类型,也不运算符重载,所以我认为Java应该从C ++和C.从完全不同的语言来处理
顺便说一句,语言名称也完全不同。
因此,我不建议在Java中键入类似于C的代码,也不建议完全键入与C ++类似的代码,因为那样的话,在Java中,您将无法使用C ++做下一步的工作,即代码根本不会继续像C ++一样,对我来说,这样修改中间的样式是不好的。
我建议也为签名操作编写和使用静态方法,因此除非在代码中只需要签名操作,否则就不要在运算符和静态方法的代码混合中看到带符号和无符号操作。仅使用运算符。
另外,我建议避免使用short,int和long基本类型,而分别使用word,dword和qword,并且您将为未签名操作和/或已签名操作调用静态方法,而不是使用运算符。
如果只打算执行带符号的操作,并且仅在代码中使用运算符,则可以使用这些基本类型short,int和long。
事实上字,双字和四字也没有在语言存在,但你可以为每个创建新类以及各自的实施应该是很容易的:
类字仅保留原始类型为short,类dword仅保留原始类型为int,而qword类仅保留原始类型为long。现在所有未签名和已签名方法都是静态的还是不是您选择的,您可以在每个类中实现,即,通过在单词类上给出含义名称来实现所有16位操作(无符号和有符号),所有32位操作(无符号和无符号)通过在dword类上给出含义名称来进行签名,而所有64位操作都可以通过在qword类上给出含义名称来进行无符号化和签名。
如果您不喜欢为每个方法提供太多不同的名称,则可以随时在Java中使用重载,这很不错,因为Java并没有将其删除!
如果要使用8位有符号运算的方法而不是运算符,而根本不需要运算符的8位无符号运算的方法,则可以创建Byte类(请注意,第一个字母'B'是大写字母,因此不是基本类型byte)并实现此类中的方法。
关于按值传递和按引用传递:
如果我没看错,就像在C#中一样,原始对象自然通过值传递,而类对象自然通过引用传递,这意味着Byte,word,dword和qword类型的对象将通过引用而不是通过值传递默认。我希望Java具有C#一样的struct对象,因此所有Byte,word,dword和qword都可以实现为struct而不是class,因此默认情况下它们是通过值传递的,而不是默认引用的,就像C#中的任何struct对象一样,例如原始类型,都是通过值传递的,而不是默认引用的,但是因为Java比C#差,所以为了解决这个问题,那么只有类和接口是通过引用传递的,默认情况下不是通过值传递的。因此,如果您想按值而不是按引用传递Byte,word,dword和qword对象,就像Java和C#中的任何其他类对象一样,您将只需要使用复制构造函数即可。
那是我能想到的唯一解决方案。我只希望我可以将基本类型的typedef定义为word,dword和qword,但是Java既不支持typedef也不使用,与C#一样,它支持using,它等效于C的typedef。
关于输出:
对于相同的位序列,可以用多种方式打印它们:二进制,十进制(如C printf中%u的含义),八进制(如C printf中%o的含义),十六进制(如%x在C printf中的含义)和整数(类似于C printf中%d的含义)。
请注意,C printf不知道作为参数传递给函数的变量的类型,因此printf仅从传递给函数第一个参数的char *对象中知道每个变量的类型。
因此,在每个类中:Byte,word,dword和qword,您可以实现print方法并获得printf的功能,即使该类的原始类型是带符号的,您仍然可以通过遵循一些涉及以下算法的方法将其打印为unsigned:逻辑和移位运算,以获取要输出到输出的数字。
不幸的是,我给您的链接没有显示如何实现这些打印方法,但是我确信您可以用Google搜索实现这些打印方法所需的算法。
以上就是我可以回答您的问题并提出建议的地方。
因为unsigned
类型是纯粹的邪恶。
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 ...
}
您是否注意到该错误?我承认我只有在调试器介入后才看到它。
由于n
是无符号类型,size_t
因此整个表达式的n - (rays.size() - 1) / 2
计算结果为unsigned
。该表达式的目的是将第一个光线从中间的一个位置签名n
:从中间一个光线的第一个光线的位置为-1,右边的第一个光线的位置为+1,依此类推。取abs值并乘以delta
角度I,我将得到n
射线与中间射线之间的角度。
对我来说不幸的是,上面的表达式包含邪恶的未签名,而不是计算为-1,而是计算为2 ^ 32-1。随后转换为double
密封该错误。
在由于误用unsigned
算术而导致的一两个错误之后,人们不得不开始怀疑,获得一个额外的位是否值得额外的麻烦。我尽力避免unsigned
在算术中使用任何类型,尽管仍将其用于非算术运算(例如二进制掩码)。
unsigned
都转换为int
,则有什么用unsigned
?它没有与可以区别的任何功能short
。而且,如果int
仅转换为混合运算(例如unsigned+int
或)unsigned+float
,则仍然会遇到的问题((unsigned)25-(unsigned)30)*1.0 > 0
,这是导致unsigned
相关Bug的主要原因。
exit(1);
真的“值得额外的麻烦”吗?能够打开大文件真的不值得经验不足的Java程序员不会搞乱的安全性unsigned
吗?
n - (rays.size() - 1) / 2
。您应该始终将二进制运算符括起来,因为代码的阅读者不需要假设任何有关计算机程序中操作顺序的内容。仅仅因为我们通常说a + b c = a +(b c)并不意味着您在阅读代码时就可以假设这一点。此外,应在循环外部定义计算,以便可以在不存在循环的情况下对其进行测试。这是一个不能确保您的类型对齐而不是无符号整数问题的错误。在C语言中,要确保您的类型对齐。
Java出于实用性原因而放弃了“ C”规范中的一些瑰宝,但随着开发人员的需求(关闭等)而逐渐回落。
我提到第一个,因为它与本次讨论有关。指针值是否符合无符号整数算法。并且,相对于该线程主题,在Java的Signed世界中维护Unsigned语义存在困难。
我想如果要让Dennis Ritchie改变自我来建议Gosling的设计团队,那会建议给Signed一个“无穷大的零”,这样所有地址偏移量请求都将首先添加其ALGEBRAIC RING SIZE来消除负值。
这样,在数组上抛出的任何偏移量都无法生成SEGFAULT。例如,在一个封装类中,我将“ RingArray of double”称为需要无符号行为的double-在“自旋转循环”上下文中:
// ...
// 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也永远不会从负索引中“获取”。请记住,也有许多合法的要求来请求先验(负)索引值。
注意:外部%modulus取消引用合法请求,而内部%modulus从比-modulus更负面的负面信息中掩盖公然恶意。如果要在Java + .. + 9 ||中出现 8 + .. +规格,那么问题就真正地变成了“无法“自转” FAULT的程序员”。
我敢肯定,上述的单行代码可以弥补所谓的Java unsigned int'缺陷'。
PS:只是为了给上面的RingArray整理提供上下文,这是一个与上面的“ 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
}
恕我直言的原因是因为他们太懒了,无法实施/纠正该错误。暗示C / C ++程序员不了解无符号,结构,联合,位标记...只是荒谬。
以前,您正在与一位基础/ bash / java程序员交谈,即将开始对la C进行编程,而对此语言没有任何真正的了解,或者您只是出于自己的想法。;)
当您每天处理来自文件或硬件的格式时,您开始质疑,他们到底在想什么。
一个很好的例子是尝试使用无符号字节作为自旋转循环。对于那些不了解最后一句话的人,您到底如何称呼自己为程序员。
直流电