前言:
以前曾经做ERP时,总是遇到尾差尾数的问题。比如:库存一箱苹果10元钱,假如一共3个苹果。拿走两个还剩多少钱。好了,言归正传,回到今天的读书笔记,精度问题。
第47条:了解和使用类库;
代码零:
public class RandomTest {
private static final Random rnd = new Random();
public static void main(String[] args) {
int n = 2*(Integer.MAX_VALUE/3);
int low = 0;
for (int i = 0; i < 1000000; i++) {
if (random(n)<n/2) {
low++;
}
}
System.out.println(low);
}
static int random(int n){
return Math.abs(rnd.nextInt())%n;
}
}
执行结果:
667068
这个结果不论多少次,都大概是这个66万左右。说明得到结果并没有均匀分布。看看我们使用的方案:
Math.abs(rnd.nextInt())%n
随机取Int值的绝对值 与 n 取余 ,也就是绝对值比n小的都过滤掉再与n取余的结果集。至于为什么没有均匀分布,不深究。
改进后的代码零:
public class RandomTest2 {
private static final Random rnd = new Random();
public static void main(String[] args) {
int n = 2*(Integer.MAX_VALUE/3);
int low = 0;
for (int i = 0; i < 1000000; i++) {
if (random(n)<n/2) {
low++;
}
}
System.out.println(low);
}
static int random(int n){
return rnd.nextInt(n);
}
}
执行结果:
499683
第48条,如果需要精确的答案,避免使用double和float;
代码一:
public class ExactTest {
public static void main(String[] args) {
System.out.println(1.03-0.42);
System.out.println(1.00-9*0.10);
}
}
执行结果:
0.6100000000000001
0.09999999999999998
这个结果是不是很意外?继续看第二段代码。代码二:
我有1元钱,第一次花了0.1,第二次花0.2,第三次花0.3,第四次花0.4,以此类推到买不起。按道理说,我应该刚好买4个商品。
@Test
public void test() {
double funds = 1.0;
int itemsBought = 0;
for (double price = 0.1; funds >= price; price += 0.1) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + "items bought.");
System.out.println("Change:$" + funds);
}
执行结果:
3items bought.
Change:$0.3999999999999999
一开始看到结果我是拒绝的。原因:double,float没办法精确表示0.1。
改进版本代码二:
@Test
public void test2() {
BigDecimal funds = new BigDecimal("1.00");
int itemsBought = 0;
final BigDecimal TEN_CENTS = new BigDecimal("0.10");
for (BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
funds = funds.subtract(price);
itemsBought++;
}
System.out.println(itemsBought + "items bought.");
System.out.println("Change:$" + funds);
}
执行结果:
4items bought.
Change:$0.00
非常棒!精确以后就用BigDecimal吧,更好的是8种想要的四舍五入都有。第49条,基本类型优先于装箱基本类型;
代码三:
@Test
public void test3() {
Comparator<Integer> naturalOrder = new Comparator<Integer>() {
@Override
public int compare(Integer first, Integer second) {
return first < second ? -1 : (first == second ? 0 : 1);
}
};
System.out.println("规则:A比B小结果-1,A与B相等结果0,A比B大结果1");
System.out.println("1和2比较的结果"+naturalOrder.compare(1, 2));
System.out.println("2和2比较的结果"+naturalOrder.compare(2, 2));
System.out.println("3和2比较的结果"+naturalOrder.compare(3, 2));
}
执行结果:
规则:A比B小结果-1,A与B相等结果0,A比B大结果1
1和2比较的结果-1
2和2比较的结果0
3和2比较的结果1
似乎、好像是正确的呢,那如果我这样测试一下呢?@Test
public void test6() {
Comparator<Integer> naturalOrder = new Comparator<Integer>() {
@Override
public int compare(Integer first, Integer second) {
return first < second ? -1 : (first == second ? 0 : 1);
}
};
Integer first = new Integer(42);
Integer second = new Integer(42);
switch (naturalOrder.compare(first, second)) {
case 1:
System.out.println("第一个数"+first+"大于第二个数"+second);
break;
case 0:
System.out.println("第一个数"+first+"等于第二个数"+second);
break;
case -1:
System.out.println("第一个数"+first+"小于第二个数"+second);
break;
default:
break;
}
}
执行结果:
第一个数42大于第二个数42
这似乎是不可理解,但看这个代码仔细分析一下:
first < second ? -1 : (first ==second ? 0 : 1)
先比较大小,拆箱。42<42所以执行后面的(first == second ? 0 : 1)
但是==这个符号却不会造成拆箱,对引用对象来说,会比较两个对象是否指向同一个引用。
Integer first =new Integer(42);
Integer second =new Integer(42);
很明显不是同一个对象引用,结果返回1。“第一个数42大于第二个数42”。
代码四:
public class ExactTest3 {
static Integer i;
public static void main(String[] args) {
if (i == 42) {
System.out.println("unbelivable");
}
}
}
执行结果:
Exception in thread "main"java.lang.NullPointerException
at com.test.ExactTest3.main(ExactTest3.java:6)
代码五:
@Test
public void test4(){
long currentTime = System.currentTimeMillis();
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum +=i;
}
System.out.println(sum);
long lastTime = System.currentTimeMillis();
System.out.println(lastTime - currentTime);
}
@Test
public void test5(){
long currentTime = System.currentTimeMillis();
long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum +=i;
}
System.out.println(sum);
long lastTime = System.currentTimeMillis();
System.out.println(lastTime - currentTime);
}
执行结果1:
2305843005992468481
8524
执行结果2:
2305843005992468481
972
结果1耗时8秒多,结果2耗时0.9秒,差了近10倍。原因在sum在代码中被错误的声明为Long包装类型。每次执行下操作时:
sum +=i;
会先将sum拆箱后执行+运算,再装箱赋值给sum。反复如此,性能极低。