Java中四则运算的那些坑

使用Java开发多年,感觉自己的水平也在不断提升,但是被Java狂虐却从来都没变过,而且任何一个Java的小角落,都能把我虐的体无完肤,但是无奈要靠Java吃饭,还得恬着脸继续使用下去。
说说最近遇到的问题,则于新工作属于互联网金融,所以里面涉及到了大量的资金计算,资金计算对数字要求的比较严谨,作为一个粗心而又自大的Java程序员,一直没把这个当回事儿,于是又被Java吊打一遍。
下面记录一下Java中四则运算的一些需要注意的小坑。

数学计算,免不了要想到 int long double 这种数据类型,但double自古在程序界就特立独行,表面上看像数学中学到的小数,实际上完全两码事儿。下面趟一下:

Double的坑

加法坑

double d1 = 0.1;
d1 += 0.1;d1 += 0.1;d1 += 0.1;d1 += 0.1;d1 += 0.1;d1 += 0.1;d1 += 0.1;d1 += 0.1; // 连着加了8次
System.out.println(d1);

0.8999999999999999

不是我们期望的0.9

减法坑

double d1 = 1.0;
double d2 = 0.1;
d1 -= d2;d1 -= d2;d1 -= d2;d1 -= d2;d1 -= d2;d1 -= d2;d1 -= d2;d1 -= d2;d1 -= d2; // 连着减了9次0.1
System.out.println(d1);

0.10000000000000014

不是我们期望的0.1

乘法坑

double d1 = 123213.0;
d1 *= 0.35;
System.out.println(d1);

43124.549999999996

不是我们期望的43124.55

除法坑

double result = 11.4/12;
System.out.println(result);

0.9500000000000001

不是我们期望的0.95

** 由此可见,如果需要对资金进行加减乘除四则运算,千万不能使用double类型的,结果总是出你意料。**

大数转为字符串坑

再试个别的,数学运算,总会使用比较大的数字吧,试一个上千万的数字吧:

double bigValue = 12345678.9;
System.out.println(bigValue);

1.23456789E7

竟然变成科学计数法了… … 这不是给人看的格式啊。
所以,如果你想要显示大的浮点数,请使用DecimalFormat自己来格式化。

除0坑

从小学数学就知道不能除以0,看看double的表现:

result = 1.0/0.0;
System.out.println(result);
result = 0.0/0.0;
System.out.println(result);

Infinity
NaN

确实不能除以0,但是结果出乎你的意料之外吧,没错,Double提供了几个方法来检查:
Double.isInfinite(double) // 是否是Infinite数
Double.isFinite(double) // 是否有有限的数
Double.isNaN(double) // 是否是这个数

NaN = Not -a-Number ,两个NaN的数一比较,竟然不相等,是不是越来越有意思了。

比较是否相等的坑

System.out.println(1.1 == 1.1? "true": "false");
System.out.println(1.1 == 1.100000000000001? "true": "false");
System.out.println(1.1 == 1.10000000000000009? "true": "false");

true
false
true

又出乎意料了

总结:
在进行资金相关的运算时,千万不能使用Double类型,否则一个不小心就让你好看。
那么不使用double,使用哪个类型合适呢?Java提供了一个叫BigDecimal的对象,专门用来做计算使用,但是BigDecimal也不是那么容易驾驭的,使用不当,还是会被爆出翔。

附:浮点数在计算机中的表示方法,提示Double为何如此诡异。

BigDecimal的坑

坑1:实例化对象

BigDecimal num = new BigDecimal(0.3);
System.out.println(num);

0.299999999999999988897769753748434595763683319091796875

这不是玩人嘛,换一种方法:

BigDecimal num = new BigDecimal("0.3");
System.out.println(num);

0.3

这总算可以了。

坑2:除法

BigDecimal d = BigDecimal.ONE;
d.divide(new BigDecimal("3"));

Exception in thread “main” java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

这是什么鬼?为毛报个异常啊…
好吧,保留两位小数试试

BigDecimal d = BigDecimal.ONE;
d.divide(new BigDecimal("3"), 2, BigDecimal.ROUND_HALF_UP);
System.out.println(d);

1

咦,为何不是0.33,数没变呢?
原因是:BigDecimal是不可变的,每次运算后返回新的对象,再改:

BigDecimal d = BigDecimal.ONE;
d = d.divide(new BigDecimal("3"), 2, BigDecimal.ROUND_HALF_UP);
System.out.println(d);

0.33

长出一口气,总算可以了。

坑3:小数部分舍入进位的坑

四舍五入,有啥可说的?还真有得说,舍入进位可以参看RoundingMode枚举,里面包含各种舍入进位,需要仔细研读哦,否则一脚一个坑。
这里要说的不是舍入进位的类型,而是说BigDecimal中提供的方法:
num.setScale(2, BigDecimal.ROUND_HALF_EVEN);
num.setScale(2, RoundingMode.HALF_EVEN);
这里面 BigDecimal.ROUND_HALF_EVEN 和 RoungingMode.HALF_EVEN 这两个东西有啥区别?
答案是没有区别,一样使用,RoungingMode 枚举是JDK1.5以后,Java支持枚举类型后加的,里面有一个oldMode,保存的就是BigDecimal中对应的常量。

坑4:保留位数的坑

将小数部分保留N位这是一个非常常用的操作,以前使用double时,都是采用:
1. 乘10的N次方
2. round
3. 除以10的N次方
这种方法来算,牛逼闪闪的BigDecimal必然会提供舍入方法,而且使用起来很简单:

BigDecimal num = new BigDecimal("1.2345678");
num = num.setScale(2, BigDecimal.ROUND_HALF_EVEN);
System.out.println(num);

1.23

事情本来很容易结束了,但是眼睛贱又扫了一眼别的地方,MathContext,这是啥?好像也是用来舍入的,试试:

MathContext ctx = new MathContext(2);
num = new BigDecimal("12.34");
num1 = new BigDecimal("56.78");
num = num.multiply(num1, ctx);  // 12.34*56.78 = 700.6652
System.out.println(num.toString());

7.0E+2

不是我们想看到700.67呀,改一下

MathContext ctx = new MathContext(3);

701

MathContext ctx = new MathContext(4);

700.7

MathContext ctx = new MathContext(5);

700.67

MathContext ctx = new MathContext(6);

700.665

明白了,这个数字是所有数字的位数,而不是小数点后的位数。

坑5:比较相等的坑

num = new BigDecimal("1");
num1 = new BigDecimal("1.0");
System.out.println(num.equals(num1)); // 竟然不相等呀
System.out.println(num.compareTo(num1)); // 0

false
0

BigDecimal的equals大大出乎意料,可不能随便使用equals方法来比较两个BigDecimal的大小了。还好compareTo还是好用的… …

时间: 2024-10-10 08:39:24

Java中四则运算的那些坑的相关文章

Java中new BigDecimal()的坑

先看一段代码示例: System.out.println(new BigDecimal(0.99)); System.out.println(new BigDecimal("0.99")); System.out.println(BigDecimal.valueOf(0.99)); System.out.println(new BigDecimal(Double.valueOf(0.99))); System.out.println(new BigDecimal(Double.valu

Java中那些踩过的坑

仅以此随笔记录一些在java中踩过而且又重踩的坑 _(:з)∠)_ 1. Runnable In ScheduledExecutorsService 当使用ScheduledExecutorService, Runnable内没有捕获的RuntimeException将会使Executor停止运行,并且异常不会被输出. ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); schedu

填坑:Java 中的日期转换

我们之前讨论过时间,在Java 中有一些方法会出现横线?比如Date 过期方法. 参考文章:知识点:java一些方法会有横线?以Date 过期方法为例 Java中的日期和时间处理方法 Date类(官方不再推荐使用,官方解释Date类不利于国际化,推荐使用Calendar类) Calendar类 DateFormat类 使用此类来时间初始化 我们发现,时间toLocalString 会有横线: vo.setSubmitDate(new Date().toLocaleString()); 可以改为:

Java中日期格式化YYYY-DD的坑

摘自:https://www.cnblogs.com/tonyY/p/12153335.html Java中日期格式化YYYY-DD的坑 2020-01-05 19:27  兔子托尼啊  阅读(115)  评论(0)  编辑  收藏 写这篇博文是记录下跨年的bug.去年隔壁组的小伙伴就是计算两个日期之间间隔的天数,因为跨年的原因计算有误. 当时测试组的小姐姐也没有模拟出来这种场景,导致上生产环境直接影响线上的数据. 今天逛技术论论坛正好遇到Java日期的操作bug. 1 yyyy 和 YYYY

java中变量命名和引用变量的一个坑

这次有两个主题,第一个太简单啦,就是java中变量的命名规则,纯记忆性东西.第二个主题,就是讨论一下对象引用变量的一个注意点. 如果你对命名规则很熟啦,就直接看第二个内容吧.(上边的图稍微有点顺序紊乱啊) 一.java中变量的命名规则 大多数语言的命名规则是相似的,只有一些微小的差别. 变量的第一个位置,可以是字母,下划线(_),美元符($) (这个在c/c++,python中是不行的)(注意:不能是数字哦,一想就知道啦为什仫) 其他的位置可以是数字,字母,下划线 不能使用java中的关键字 j

java 中==符号的坑

在某技术群看到这样的一个面试题目: 这是一个4年经验的java 从业者的答案. 你的答案是什么呢? 正确的答案是true. 为什么? 其实当使用String a="a"+"b"+1;时,程序会建立一个String缓冲池(String pool):把a放入:当再次使用Stirng b="ab1";程序首先会在这个String缓冲池中寻找相同值的对象;找到了a,然后a,b引用了相同的值的对象. 我们可以查找到很多相关与String pool的资料.

浅谈Java中的补零扩展和补符号位扩展

今天,魏屌出了一道题,题目如下: 定义一个大头序的byte[]a={-1,-2,-3,-4},转换成short[]b.问b[0]和b[1]分别是多少? 乍一看,这题不难,无非就是移位操作,再进行组合.但是呢?对于用Java的童鞋来说,这里面有一个坑,稍不注意可能就踩进去了.在说之前,我先把代码和答案贴出来吧. 看到这里,可能有的童鞋比较奇怪,为啥要&0xff,这不相当于没变化吗?非也,不信我举个例子. 答案是-127和129.很奇怪不是吗?我想的明明都是-127啊!!! 解答这个问题之前,我们先

1000行代码徒手写正则表达式引擎【1】--JAVA中正则表达式的使用

简介: 本文是系列博客的第一篇,主要讲解和分析正则表达式规则以及JAVA中原生正则表达式引擎的使用.在后续的文章中会涉及基于NFA的正则表达式引擎内部的工作原理,并在此基础上用1000行左右的JAVA代码,实现一个支持常用功能的正则表达式引擎.它支持贪婪匹配和懒惰匹配:支持零宽度字符(如"\b", "\B"):支持常用字符集(如"\d", "\s"等):支持自定义字符集("[a-f]","[^b-

Java中的夏令时问题

因为在用C#的时候被夏令时,由于不知道被坑过一回,所以这次将在java中的时区转换信息做一下记录,很简单 SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); inputFormat.setTimeZone(TimeZ