Java 隐式转换引发的生产事故,菜是原罪,切记切记~

背景:公司项目最近做项目技术建设,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);

结果展示:

在这里插入图片描述
这前后修改的输出结果不一致,这不要命了吗。。

躺坑分析:

  1. Math.ceil() 方法要求入参是 double 类型,所以传值 dataset/50000 会包含隐式类型转换,提示可能存在精度丢失bug;
  2. 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);

输出结果:
在这里插入图片描述
这里面涉及到一个低精度到高精度的隐式转换:

  1. double c = a/ b; (我们肉眼所看到的)
  2. c = ( 10 / 3 ) = (double) 3 = 3.0;(程序实际做的)
  3. 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 ;

隐式转换规则

  1. 从小到大可以隐式转换;
  2. 从大到小会编译报错;
    在这里插入图片描述

保留指定小数位

选择保选择保留小数位数:

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)范围内的随机数

感 谢 各 位 大 佬 的 阅 读,随 手 点 赞,日 薪 过 万~! !!
  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhuzicc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值