Java解惑——有感1

以后在这个分类里面写的所有都是参考《Java 解惑》里的内容来写的。可以这么说,只有程序部分内容是自己添加的,只是为了更好地理解。

谜题1:奇数性

源程序:

ContractedBlock.gifExpandedBlockStart.gifOddity
public class Oddity {

/*
*当取余操作返回一个非零的结果时,它与左操作数具有相同的正负符号.
*无论你何时使用到了取余操作符,都要考虑到操作数和结果的符号。
*该操作符的行为在其操作数非负时是一目了然的,
*但是当一个或两个操作数都是负数时,它的行为就不那么显而易见了。
*
*/
public static boolean isOdd1(int i) {
int remainder = i % 2;
//System.out.println(i+"的余数="+remainder);
return remainder == 1;
}

public static boolean isOdd2(int i) {
int remainder = i % 2;
//System.out.println(i+"的余数="+remainder);
return remainder != 0;
}

public static boolean isOdd3(int i) {
//System.out.println(Integer.toBinaryString(i));
//System.out.println(Integer.toBinaryString(1));
int remainder = i & 1;
//System.out.println(Integer.toBinaryString(remainder));
//System.out.println(i+"& 1="+remainder);
return remainder != 0;
}

public static void main(String[] args) {
int[] data = {5,4,0,-5,-4};
String str
= "";
System.out.println(
"调用isOdd1的结果:");
for(int i=0;i<data.length;i++){
if(isOdd1(data[i]) == true){
str
= "奇数";
}
else{
str
= "偶数";
}
System.out.println(data[i]
+" is "+str);
}

System.out.println(
"调用isOdd2的结果:");
for(int i=0;i<data.length;i++){
if(isOdd2(data[i]) == true){
str
= "奇数";
}
else{
str
= "偶数";
}
System.out.println(data[i]
+" is "+str);
}

System.out.println(
"调用isOdd3的结果:");
for(int i=0;i<data.length;i++){
if(isOdd3(data[i]) == true){
str
= "奇数";
}
else{
str
= "偶数";
}
System.out.println(data[i]
+" is "+str);
}
System.out.println(
"-5/-2="+-5/-2);
System.out.println(
"-5%-2="+-5%-2);
}
}

谜题2:找零时刻

源程序:

ContractedBlock.gifExpandedBlockStart.gifView Code
/*
*问题在于1.1这个数字不能被精确表示成为一个double,因此它被表示成为最接近它的double值.
*该程序从2中减去的就是这个值。遗憾的是,这个计算的结果并不是最接近0.9的double值。
*表示结果的double值的最短表示就是你所看到的打印出来的那个可恶的数字。
*更一般地说,问题在于并不是所有的小数都可以用二进制浮点数来精确表示的。
*/
import java.math.BigDecimal;

public class Change {
public static void main(String args[]) {

System.out.println(
2.00 - 1.10);

System.out.printf(
"%.2f%n",2.00 - 1.10);

System.out.println((
200 - 110)+" cents");

BigDecimal minuend
= new BigDecimal("2.00");
BigDecimal subtrahend
= new BigDecimal("1.10");
System.out.println(
"2.00 - 1.10 ="+minuend.subtract(subtrahend));
}
}

总之, 在需要精确答案的地方,要避免使用float和double;对于货币计算,要使用int、long或BigDecimal。对于语言设计者来说,应该考虑对小数运算提供语言支持。一种方式是提供对操作符重载的有限支持,以使得运算符可以被塑造为能够对数值引用类型起作用,例如BigDecimal。另一种方式是提供原始的小数类型,就像COBOL与PL/I所作的一样。

谜题3:长整除

源程序:

ContractedBlock.gifExpandedBlockStart.gifView Code
public class LongDivision {

public static void main(String[] args) {

final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
//System.out.println("MICROS_PER_DAY = "+MICROS_PER_DAY);
//System.out.println("MILLIS_PER_DAY = "+MILLIS_PER_DAY);

System.out.println(MICROS_PER_DAY
/ MILLIS_PER_DAY);

long MICROS_PER_DAY1 = 24L * 60 * 60 * 1000 * 1000;
long MILLIS_PER_DAY1 = 24L * 60 * 60 * 1000;
//System.out.println("MICROS_PER_DAY1 = "+MICROS_PER_DAY1);
//System.out.println("MILLIS_PER_DAY1 = "+MILLIS_PER_DAY1);

System.out.println(MICROS_PER_DAY1
/ MILLIS_PER_DAY1);
}
}

问题在于常数MICROS_PER_DAY的计算“确实”溢出了。尽管计算的结果适合放入long中,并且其空间还有富余,但是这个结果并不适合放入int中。这个计算完全是以int运算来执行的,并且只有在运算完成之后,其结果才被提升到long,而此时已经太迟了:计算已经溢出了,它返回的是一个小了200倍的数值。从int提升到long是一种拓宽原始类型转换(widening primitive conversion),它保留了(不正确的)数值。这个值之后被MILLIS_PER_DAY整除,而MILLIS_PER_DAY的计算是正确的,因为它适合int运算。这样整除的结果就得到了5。

那么为什么计算会是以int运算来执行的呢?因为所有乘在一起的因子都是int数值。当你将两个int数值相乘时,你将得到另一个int数值。Java不具有目标确定类型的特性,这是一种语言特性,其含义是指存储结果的变量的类型会影响到计算所使用的类型。

这个教训很简单:当你在操作很大的数字时,千万要提防溢出——它可是一个缄默杀手。即使用来保存结果的变量已显得足够大,也并不意味着要产生结果的计算具有正确的类型。当你拿不准时,就使用long运算来执行整个计算。

语言设计者从中可以吸取的教训是:也许降低缄默溢出产生的可能性确实是值得做的一件事。这可以通过对不会产生缄默溢出的运算提供支持来实现。程序可以抛出一个异常而不是直接溢出,就像Ada所作的那样,或者它们可以在需要的时候自动地切换到一个更大的内部表示上以防止溢出,就像Lisp所作的那样。这两种方式都可能会遭受与其相关的性能方面的损失。降低缄默溢出的另一种方式是支持目标确定类型,但是这么做会显著地增加类型系统的复杂度。

谜题4:初级问题

源程序:

ContractedBlock.gifExpandedBlockStart.gifView Code
/*这里确实有一个教训:在long型字面常量中,一定要用大写的L,千万不要用小写的l。*/
public class Elementary {

public static void main(String[] args) {

System.out.println(
12345 + 5432l);

System.out.println(
12345 + 5432L);
}
}

谜题5:十六进制的趣事

源程序:

ContractedBlock.gifExpandedBlockStart.gifView Code
public class JoyOfHex {

public static void main(String[] args) {

System.out.println(Long.toHexString(
0x100000000L + 0xcafebabe));

System.out.println(
"0x100000000L的二进制表示:"+Long.toBinaryString(0x100000000L));
System.out.println(
"0xcafebabe的二进制表示:"+Integer.toBinaryString(0xcafebabe));

System.out.println(
"0xcafebabeL的二进制表示:"+Long.toBinaryString(0xcafebabeL));
System.out.println(Long.toHexString(
0x100000000L + 0xcafebabeL));
}
}

十进制字面常量具有一个很好的属性,即所有的十进制字面常量都是正的,而十六进制或是八进制字面常量并不具备这个属性。要想书写一个负的十进制常量,可以使用一元取反操作符(-)连接一个十进制字面常量。以这种方式,你可以用十进制来书写任何int或long型的数值,不管它是正的还是负的,并且负的十进制常数可以很明确地用一个减号符号来标识。但是十六进制和八进制字面常量并不是这么回事,它们可以具有正的以及负的数值。如果十六进制和八进制字面常量的最高位被置位了,那么它们就是负数。在这个程序中,数字0xcafebabe是一个int常量,它的最高位被置位了,所以它是一个负数。它等于十进制数值-889275714。

该程序执行的这个加法是一种“混合类型的计算(mixed-type computation):左操作数是long类型的,而右操作数是int类型的。为了执行该计算,Java将int类型的数值用拓宽原始类型转换提升为一个long类型,然后对两个long类型数值相加。因为int是一个有符号的整数类型,所以这个转换执行的是符合扩展:它将负的int类型的数值提升为一个在数值上相等的long类型数值。

这个加法的右操作数0xcafebabe被提升为了long类型的数值0xffffffffcafebabeL。这个数值之后被加到了左操作数0x100000000L上。当作为int类型来被审视时,经过符号扩展之后的右操作数的高32位是-1,而左操作数的高32位是1,将这两个数值相加就得到了0,这也就解释了为什么在程序输出中前导1丢失了。下面所示是用手写的加法实现。(在加法上面的数字是进位。)

    1111111

  0xffffffffcafebabeL

+ 0x0000000100000000L

---------------------

  0x00000000cafebabeL

订正该程序非常简单,只需用一个long十六进制字面常量来表示右操作数即可。


发布了137 篇原创文章 · 获赞 50 · 访问量 21万+

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览