背景:公司项目最近做项目技术建设,SonarLint 对历史代码扫描出的 Bug 进行 ”清零“,故障就此发生。在修改后上线,导致生产环境所有的导出功能全部崩溃!在此复盘跟各位大佬分享一波,基础真的很重要!
先给大佬们看看灾区代码:
// 修改之前:
int pageCount = (int)Math.ceil(dataset.size()/50000)+1;
// ======== 分割线 ========
// (不要小看这一行, 这是一个导出工具类中的代码)
int pageCount = (int)Math.ceil(dataset.size()/50000.0D)+1;
修改之前的代码,SonarLint 扫描提示:
注意修改之后的 50000.0D,故事来了!做个实验:
int dataset = 13;
int pageCount1 = (int)Math.ceil(dataset/50000)+1;
int pageCount2 = (int)Math.ceil(dataset/50000.0D)+1;
System.out.println("修改之前:"+pageCount1);
System.out.println("修改之后:"+pageCount2);
结果展示:
这前后修改的输出结果不一致,这不要命了吗。。
躺坑分析:
Math.ceil()
方法要求入参是double
类型,所以传值 dataset/50000 会包含隐式类型转换,提示可能存在精度丢失bug;Math.ceil()
方法本身就是向上取整,所以当把 50000 改成 50000.0D 以后:- 我们看到代码:dataset/50000.0D;
- 程序实际做的事是先把
dateset
变量转换成double
类型(低精度向高精度转型),然后才会进行运算; - 再加上
Math.ceil()
本身向上取整,所以导致本次故障的产生;
取整运算
咱们直接看代码︰
int a = 10;
int b = 3;
double c = a / b;
System.out.println(c);
输出结果:
这里面涉及到一个低精度到高精度的隐式转换:
- double c = a/ b; (我们肉眼所看到的)
- c = ( 10 / 3 ) = (double) 3 = 3.0;(程序实际做的)
- Java当中的整数除法采用的是向0舍入的方式;(例如:10/3 = 3,直接取整数位,不考虑小数位)
第二段代码:
int a = 10;
int b = 3;
double c = (double) a / b;
System.out.println(c);
输出结果:
其中 double c = (double) a / b; 等价于double c = 10.0 /3.0 ;
隐式转换规则
- 从小到大可以隐式转换;
- 从大到小会编译报错;
保留指定小数位
选择保选择保留小数位数:
public void test() {
int a = 10;
int b = 3;
double c = a/b;
System.out.println("a/b:"+a/b);
System.out.println("(double) a/b:"+(double) a/b);
System.out.println("c:"+c);
System.out.println("df.format((double) a/b):"+df.format((double) a/b));
}
输出结果:
结论
减乘除计算,首先会统一操作数的精度,其规则是按高精度的操作数统一;
- 例如:double型 / int型、int型 / double型,最终都转成double型 / double型。
求余运算
Java 中取余运算符 %
一个双目运算符,它的操作数通常是正/负整数、浮点数。
注意:如果负数参与此运算,则结果的正负取决于前面一个数是正数还是负数。
对于整数,Java 的取余运算规则为:a % b = a - ( a / b ) * b
示例:
5%3=5-(5/3)*3=2
5%-3=5-(5/-3)*-3=2
-5%3=-5-(-5/3)*3=-2
-5%-3=-5-(-5/-3)*-3=-2
如果操作数中有浮点数则采用的规则为:a % b = a - ( b * q )
示例:
5.2%3.1=5.2-1*3.1=2.1
5.2%-3.1=5.2-(-1)*(-3.1)=2.1
-5.2%3.1=-5.1-(-1)3.1=-2.1
-5.2%-3.1=-5.1-(-1)*(-3.1)=-2.1
代码示例:
public void test2 {
int a = 13 / 5;
int b = 13 % 5;
int c = 5 / 13;
int d = 5 % 13;
int e = 13 / -5;
int f = -13 / 5;
int h = -13 % 5;
int j = 13 % -5;
System.out.println("int a = 13 / 5 = " + a);
System.out.println("int b = 13 % 5 = " + b);
System.out.println("int c = 5 / 13 = " + c);
System.out.println("int d = 5 % 13 = " + d);
System.out.println("int e = 13 / -5 = " + e);
System.out.println("int f = -13 / 5 = " + f);
System.out.println("int h = -13 % 5 = " + h);
System.out.println("int j = 13 % -5 = " + j);
}
输出结果:
附赠Math函数
进位方法
方法 | 说明 |
---|---|
Math.round() | 四舍五入,入参为float时返回int值,入参为double时返回long值; |
Math.rint() | 四舍五入,返回与参数最接近的整数,如果两边一样接近,那么选取偶数; |
Math.ceil() | 向上取整,逢余进一; |
Math.floor() | 向下取整,逢余舍一; |
示例:
System.out.println("--------- Math.ceil: ---------");
System.out.println("Math.ceil(1.1):"+Math.ceil(1.1));
System.out.println("Math.ceil(1.5):"+Math.ceil(1.5));
System.out.println("Math.ceil(-1.1):"+Math.ceil(-1.1));
System.out.println("Math.ceil(-1.5):"+Math.ceil(-1.5));
System.out.println("--------- Math.floor: ---------");
System.out.println("Math.floor(1.1):"+Math.floor(1.1));
System.out.println("Math.floor(1.5):"+Math.floor(1.5));
System.out.println("Math.floor(-1.1):"+Math.floor(-1.1));
System.out.println("Math.floor(-1.5):"+Math.floor(-1.5));
System.out.println("--------- Math.rint: ---------");
System.out.println("Math.rint(1.1):"+Math.rint(1.1));
System.out.println("Math.rint(1.5):"+Math.rint(1.5));
System.out.println("Math.rint(2.5):"+Math.rint(1.6));
System.out.println("Math.rint(-1.1):"+Math.rint(-1.1));
System.out.println("Math.rint(-1.5):"+Math.rint(-1.5));
System.out.println("Math.rint(-2.5):"+Math.rint(-1.6));
System.out.println("--------- Math.round: ---------");
System.out.println("Math.round(1.1):"+Math.round(1.1));
System.out.println("Math.round(1.5):"+Math.round(1.5));
System.out.println("Math.round(-1.1):"+Math.round(-1.1));
System.out.println("Math.round(-1.5):"+Math.round(-1.5));
算数方法
方法 | 说明 |
---|---|
Math.max(a, b) | 计算最大值 |
Math.min(a, b) | 计算最小值 |
Math.abs() | 取绝对值 |
Math.pow(a, b) | 计算a的b次方 |
Math.sqrt() | 计算平方根 |
Math.cbrt() | 计算立方根 |
示例:
System.out.println("Math.sqrt():"+Math.sqrt(8));
System.out.println("Math.cbrt():"+Math.cbrt(8));
System.out.println("Math.pow():"+Math.pow(3, 2));
System.out.println("Math.max():"+Math.max(1, 2));
System.out.println("Math.min():"+Math.min(1, 2));
随机数
方法 | 说明 |
---|---|
Math.random() | 取得一个[0, 1)范围内的随机数 |
![](https://i-blog.csdnimg.cn/blog_migrate/375c20d2fe2c219c3950c62d1629eb70.png)