关键词:四舍五入、向下舍入、精度问题、前端、后端、前后端
相关问题:金额计算问题,雪花id精度丢失问题
目录:
后端 BigDecimal 四舍五入报错
前端 toFixed 既不是四舍五入,也不是向下或向上舍入
后端
后端总体来说还好,主要整理一下 BigDecimal 四舍五入问题。
BigDecimal 四舍五入未指定舍入方式
在开发中遇到了同事写的一个代码报错,是 BigDecimal 四舍五入保留两位小数时未指定舍入方式导致的。
话不多说,上问题代码(源代码逻辑比较复杂,新写了一个等效代码):
import java.math.BigDecimal; public class Example { public static void main(String[] args) { BigDecimal number = new BigDecimal("3.1456"); BigDecimal roundedNumber = number.setScale(2); // 如果小数位数大于2位,这里会报错 System.out.println(roundedNumber); } }
于是乎,问题就出现在 number.setScale(2) 这里了,看报错:
java.lang.ArithmeticException: Rounding necessary at java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4148) ~[na:1.8.0_191] at java.math.BigDecimal.needIncrement(BigDecimal.java:4204) ~[na:1.8.0_191] at java.math.BigDecimal.divideAndRound(BigDecimal.java:4112) ~[na:1.8.0_191] at java.math.BigDecimal.setScale(BigDecimal.java:2452) ~[na:1.8.0_191] at java.math.BigDecimal.setScale(BigDecimal.java:2512) ~[na:1.8.0_191] .....
解决方法也很简单,setScale(2) 改成 setScale(2, RoundingMode.HALF_UP) 就好了,上代码:
import java.math.BigDecimal; import java.math.RoundingMode; public class Example { public static void main(String[] args) { BigDecimal number = new BigDecimal("3.1456"); BigDecimal roundedNumber = number.setScale(2, RoundingMode.HALF_UP); System.out.println(roundedNumber); } }
再次完整复现下问题:
public static void main(String[] args) { BigDecimal bigDecimal; String string; bigDecimal = new BigDecimal("3.1"); // 正常 不推荐(idea提示WARNING) string = bigDecimal.setScale(2).toString(); // 正常 不推荐(idea提示WARNING) string = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).toString(); // 正常 推荐 string = bigDecimal.setScale(2, RoundingMode.HALF_UP).toString(); bigDecimal = new BigDecimal("3.141"); // 报错 不推荐(idea提示WARNING) try { string = bigDecimal.setScale(2).toString(); } catch (ArithmeticException e) { e.printStackTrace(); } // 正常 不推荐(idea提示WARNING) string = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).toString(); // 正常 推荐 string = bigDecimal.setScale(2, RoundingMode.HALF_UP).toString(); }
关于 RoundingMode,网上是这么说的,可以参考一下:
Java 中的 BigDecimal 类中的 RoundingMode 属性用于设置舍入模式。该属性可以取以下枚举值:
RoundingMode.UP: 向远离零的方向舍入
RoundingMode.DOWN: 向接近零的方向舍入
RoundingMode.CEILING: 向正无穷方向舍入
RoundingMode.FLOOR: 向负无穷方向舍入
RoundingMode.HALF_UP: 向最近的整数,如果距离两边相等则向上舍入
RoundingMode.HALF_DOWN: 向最近的整数,如果距离两边相等则向下舍入
RoundingMode.HALF_EVEN: 向最近的整数,如果距离两边相等则向偶数舍入
RoundingMode.UNNECESSARY: 不进行舍入操作,如果存在非精确结果则抛出 ArithmeticException 异常
主键雪花ID太长导致前端 JSON.parse() 时后几位精度丢失
其实这时前端的问题,但是前端不好解决,所以从后端SpringBoot接口返回入手解决,将雪花id从Long转成String即可解决。
可以按照 mybatis-plus 官网说的方法解决,下面摘录一下解决方法。
原文档地址:ID_WORKER 生成主键太长导致 js 精度丢失
JavaScript 无法处理 Java 的长整型 Long 导致精度丢失,具体表现为主键最后两位永远为 0,解决思路: Long 转为 String 返回
FastJson 处理方式
@Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter(); FastJsonConfig fjc = new FastJsonConfig(); // 配置序列化策略 fjc.setSerializerFeatures(SerializerFeature.BrowserCompatible); fastJsonConverter.setFastJsonConfig(fjc); converters.add(fastJsonConverter); }
JackJson 处理方式
方式一
// 注解处理,这里可以配置公共 baseEntity 处理 @JsonSerialize(using=ToStringSerializer.class) public long getId() { return id; }
方式二
// 全局配置序列化返回 JSON 处理 final ObjectMapper objectMapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); objectMapper.registerModule(simpleModule);
比较一般的处理方式:增加一个 public String getIdStr() 方法,前台获取 idStr
前端
前端的坑可就多了,肯定还有我没整理到的,大家如果有遇到,可以顺手在下面评论一下,我会整理进来,感谢!
金额千分位
JavaScript 数字添加千分位问题我之前整理过,见:https://www.only4.work/blog/?id=514
Number.toFixed() 不是四舍五入,也不是向下舍入
开始之前,先说个题外话:
四舍五入有个小技巧,不记得是在哪本书上看到的(不过那本书作者好像也说得是不记得在哪学到的了(笑))。
保留整数四舍五入 等价于 加 0.5 后向下舍入
保留一位小数四舍五入 等价于 加 0.05 后向下舍入
不能使用toFixed(x)去进行舍入操作,因为IEEE754标准规定的浮点数取整算法是银行家舍入法,即四舍六入五留双法:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。
关于浮点数值计算会产生摄入误差的问题,有一点需要明确:这是使用基于IEEE754数值的浮点计算的通病,ESMAScript并非独此一家,其他使用相同数值格式的语言也存在这个问题。
——《JS高级程序设计(第3版)》3.4.5 Number类型
说完了,下面复现一下 toFixed() 的问题。测试代码:
var numList = [ 1.44, 1.45, 2.44, 2.45, 1.55, 2.55, 3.55, 4.55, 2.65, 3.65, -3.5 ] // numList.forEach(num => console.log(num, num.toFixed(1))) numList.forEach(num => { // 保留一位小数四舍五入 = 加 0.05 后向下舍入 let round = Math.floor(num * 10 + 0.5) / 10 // 前端 Number.toFixed 方法 let toFixed = +num.toFixed(1) // 前端 Math.round 方法 let mathRound = Math.round(num * 10) / 10 console.log(num, 'round:', round, 'mathRound:', mathRound, ...round !== mathRound ? ['round !== mathRound'] : [], 'toFixed:', toFixed, ...round !== toFixed ? ['round !== toFixed'] : []) })
BTW(顺便说一句),Math.round() 以及其他的一些 Math 方法可能会有精度问题。
前端的一些坑
let testCase = [ () => 0.07 * 100, // 7.000000000000001 () => 0.1 + 0.2, // 0.30000000000000004 () => 2.3 + 2.4, // 4.699999999999999 () => [Math.round(-3.5), (-3.5).toFixed(0), -3.5.toFixed(0)], // [-3, '-4', -4] () => [Math.round(3.5), 3.5.toFixed(0), +3.5.toFixed(0)], // [4, '4', 4] () => [-'4', +'4'], // [-4, 4] () => [+'+4', +'-4', -'+4', -'-4'], // [4, -4, -4, 4] () => [+'+0', +'-0', -'+0', -'-0'], // [0, -0, -0, 0] ] for (let t of testCase) { console.log(t, t()) }
前端数字计算相关轮子
前端感觉还比较复杂,感觉坑可能会比较多,所以个人觉得,如果是上生产环境,可能使用别人封装好的轮子会比自己把问题全踩一遍更快(更能保住工作哈哈),如果是自己研究学习的话,倒是可以多研究研究,看看有什么坑,这样之后同事遇到同样问题,就可以第一眼发现然后无情嘲笑他了(嘿嘿,我真坏)
在网上搜了一下相关轮子,但是没有实际验证过,只是简单看了下文档和GitHub,列出了最近有更新的,如下:
currency.js
A small, lightweight javascript library for working with currency values.
GitHub: https://github.com/scurker/currency.js
官网: https://currency.js.org/
npm: https://www.npmjs.com/package/currency.js
前端处理金额精度问题和千分位格式化
https://www.jianshu.com/p/df0f6583e405
math.js
An extensive math library for JavaScript and Node.js
GitHub: https://github.com/josdejong/mathjs
官网: https://mathjs.org/
npm: https://www.npmjs.com/package/math.js
decimal.js
An arbitrary-precision Decimal type for JavaScript.
官网:
GitHub: https://github.com/MikeMcl/decimal.js
npm: https://www.npmjs.com/package/decimal.js
bignumber.js
A JavaScript library for arbitrary-precision decimal and non-decimal arithmetic.
官网:
GitHub: https://github.com/MikeMcl/bignumber.js
npm: https://www.npmjs.com/package/bignumber.js
本文由张小弟之家原创,转载请注明出处(blog.only4.work)并在本文下方评论下转载地址,感谢~
本站文章除注明转载/出处外,均为原创,若要转载请务必注明出处。转载后请将转载链接通过邮件告知我站,谢谢合作。本站邮箱:admin@only4.work
尊重他人劳动成果,共创和谐网络环境。点击版权声明查看本站相关条款。