奇数性
看下面代码时候是否能判断参数 i 是奇数?
1public static boolean isOdd(int i){
2 return i % 2 == 1;
3}
答案是:No!看似正确的判断奇数,但是如果 i 是负数,那么它返回值都是 false 造成这种现象的是 => 从思想上固化,认为奇数只在正数范围,故判断负数将报错,在 C++ 中也是,负数取余还是负。在 Java 中取余操作定义产生的后果都满足下面的恒等式:
1//int数值a, 与非零int数值b 都满足下面的等式:
2(a / b) * b + (a % b) == a
从上面就可以看出,当取余操作返回一个非零的结果时,左右操作数具有相同的正负号,所以当取余在处理负数的时候,以及会考虑负号。
而上面的这个问题解决方法就是避免判断符号:
1public static boolean isOdd(int i){
2 return i % 2 != 0;
3}
让结果与 0 比较,很容易避免正负号判断。
思考:在使用取余操作的时候要考虑符号对结果的影响。在运算中尝试使用 0 解决符号问题,在一定程度上避免符号对结果的影响。
浮点数产生的误差
看下面代码会打印出什么样的结果?
1public class Change{
2 public static void main(String args[]){
3 System.out.println(2.00 - 1.10);
4 }
5}
从主观上看,打印的结果必然是 0.90,然后这却是一个主观错误。对于 1.10 这个数,计算机只会使用近似的二进制浮点数表示,产生精度影响。从上面的例子中来看,1.10 在计算机中表示为 1.099999,这个 1.10 并没有在计算机中得到精确的表示。针对这个精度问题,我们可能会选择 System.out.printf("%.2f%n", 2.00 - 1.10); 解决。
尽管打印出来的是正确答案,但是依旧会暴露出一个问题:如果精度控制在 2.00 - 1.0010,那么精度误差依旧会出现。这里也说明使用 printf,计算机底层依旧是使用二进制的方式来计算,只不过这种计算提供了更好的近似值而已。
那么应该怎么解决这个问题呢?首先想到是使用 int 模拟小数每一位,然后计算,最后将结果又转化为小数,以此想到的就是使用 BigDecimal 类,它主要用于精确小数运算。
1import java.math.BigDecimal;
2public class Change1{
3 public static void main(String args[]){
4 System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10")));
5 }
6}
通过上面的代码就能得到一个精确的值。
注:使用 BigDecimal 的时候不要使用 BigDecimal(double d) 的构造方法,在 double 与 double 之间传值的时候依旧会引起精度损失,这是一个严重的问题。BigDecimal 底层采用的就是 int[],使用 String 的时候会将 String 不断取每一位存入 int[],使用 double 的时候同理将数字的每一位存入 int[],但是 double 本身存在误差,导致存入的数据会出现误差,例如 0.1 存入 double 就表示为 0.1000000099999999,因此不使用 double 类型的构造函数。
思考:对于精确要求不高的地方,完全可以使用 float/double,但是对于要求精度的计算,比如货币则一定要使用 int、long、BigDecimal。