全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-06-02_掌握BigDecimal:详解其原理及最佳实践

您的位置:首页 >> 新闻 >> 行业资讯

掌握BigDecimal:详解其原理及最佳实践 点击关注公众号,“技术干货”及时达!?思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航?? ?BigDecimal是Java中用于浮点数数值计算的类,其主要适合用于处理需要精确表示和运算的场景。「BigDecimal不仅能精确表示非常大的或非常小的数字,同时还提供任意精度的运算。其有效的解决了浮点数(float和double)在进行精确计算时可能出现的舍入误差问题。」 本文主要介绍了BigDecimal数据存储的原理以及开发中BigDecimal使用的最佳实践。以加深读者对于BigDecimal的理解。 BigDecimal简介在处理金融、科学等领域的计算时,为了解决double或float在计算值存在的精度缺失问题BigDecimal应运而生。BigDecimal在设计之初「皆在提供更高的精度和准确性,以确保浮点数运算的准确性」。因此其具有如下特点: 「高精度」:BigDecimal能够精确表示非常大的或非常小的数字,并且提供任意精度的运算。「不可变性」:BigDecimal对象是不可变的。一旦创建,数值就不会改变。所有的算术运算都会返回一个新的BigDecimal对象,而不会修改原来的对象。这种设计使得BigDecimal是线程安全的。「丰富的运算方法」:BigDecimal提供了丰富的算术运算方法,如add(加法)、subtract(减法)、multiply(乘法)和divide(除法),以及用于舍入、取整和比较的方法。「灵活的舍入模式」:提供多种舍入模式(如四舍五入、向上取整等),确保结果的精度和舍入行为可控。总的来看,「BigDecimal通过其对象的不可变性,从而确保了线程安全;与此同时,其还并提供丰富的算术运算方法(如加法、减法、乘法、除法)和多种舍入模式(如四舍五入、向上取整等),从而满足精确数值计算的需求。」 BigDecimal数据存储的秘密对BigDecimal有了基础认识后,接下来我们便通过Debug的形式来看看BigDecimal内部究竟是如来实现数据的高精度的存储的。为此我们首先通过如下的语句来构建一个BigDecimal对象 BigDecimalbigDecimal=newBigDecimal("3.1415926"); 运行代码进入Idea的Debug模式后,可以看到如下内容: 不难发现,对于BigDecimal对象而言其内部有 「intVal、scal、precision、stringCache、initCompact等五个重要属性。」 进一步,翻开BigDecimal源码,可以看到这五个属性各自对应的类型: publicclassBigDecimalextendsNumberimplementsComparableBigDecimal{ privatefinalBigIntegerintVal; privatefinalintscale; privatetransientintprecision; privatetransientStringstringCache; privatefinaltransientlongintCompact; 具体来看,「intVal为一个BigInteger对象,其主要用于保存超出基本类型的数值。」 例如:对于Long数据类型来看,其最大类型为0x7fffffffffffffff即9223372036854775807。因此如下的赋值BigDecimal bigDecimal = new BigDecimal("9223372036854775808")其已然超出了Java中基础类型所能表示的范围,而此时在bigDecimal对象中,其内部的intVal如下所示,不难发现9223372036854775808被赋值给intVal。 image.png明白了BigDecimal中intVal属性的存储规则后,再来看其中的scale、precision所标示的含义。「其中scale表示小数点后的位数而precision则代表BigDecimal中数据的总位数,即包括整数和小数部分。」 进一步,「BigDecimal的stringCache属性则主要用于保存BigDecimal数据所转成的字符串信息,而intCompact则用于将long数值以内的数据转为基本数据类型long进行存储。」 (注:如果数据类型范围超过long所能表示的范围,则会将数据保存至intVal中) 此外还要注意一点,如果是包含小数点的数据其会将其小数点去掉,进而保存其去掉小数点后的数据。例如new BigDecimal("3.1415926")在该BigDecimal对象中intCompact = 31415926。 BigDecimal的最佳实践知晓了BigDecimal内部对于浮点数据的存储原理后,接下来我们来谈一谈有关BigDecimal的几点最佳实践,以避免在使用BigDecimal时踩坑。 ?为了避免精度丢失,尽量使用BigDecimal(String val)构造方法或者BigDecimal.valueOf(double val)?如果使用double 类型的数据来构建一个 BigDecimal 对象时,其会出现精度丢失的问题。这主要是因为 double 类型本身在表示浮点数时存在精度限制。 具体来看,double 类型使用 IEEE 754标准的双精度浮点数格式,该格式在二进制表示中无法精确地表示所有十进制的小数。例如,十进制数 0.1 在二进制浮点数中是一个无限循环小数,只能近似表示为 0.1000000000000000055511151231257827021181583404541015625。而使用 new BigDecimal(double) 构造函数时double类型的数值的会将其近似值传递给 BigDecimal,进入导致精度丢失。例如: doublevalue=0.1; BigDecimalbd=newBigDecimal(value); System.out.println(bd); 上述代码最终会输出:0.1000000000000000055511151231257827021181583404541015625而我们所期待的 BigDecimal 实际为 0.1。因此为了避免构建BigDecimal时出现精度丢失的问题,「推荐使用它的BigDecimal(String val)构造方法或者BigDecimal.valueOf(double val)静态方法来创建对象。」 ?使用 BigDecimal进行除法运算时,指明数据结果的精度?BigDecimal 在进行除法运算时,「如果不指定截取的精度和舍入模式,当出现数据无法整除时,会出现 ArithmeticException 异常」。例如 1 / 3时其会得到一个无限循环小数。这时如果没有明确指定精度和舍入方式,BigDecimal 将无法完成除法运算并抛出异常。 publicclassBigDecimalDivisionExample{ publicstaticvoidmain(String[]args){ BigDecimalnum1=newBigDecimal("1"); BigDecimalnum2=newBigDecimal("3"); BigDecimalresult=num1.divide(num2); } 在上述代码中,num1 / num2的结果为一个无限循环小数 0.333...。「由于我们并未在代码中指定精度和舍入模式,所以当执行上述代码时如出现如下异常」:Exception: Non-terminating decimal expansion; no exact representable decimal result. 为了避免上述异常的发生,可以再执行divide显示的指定精度截取方式。具体方式如下:num1.divide(num2,2, RoundingMode.HALF_UP);在本例中对数据保留了两位小数,同时使用RoundingMode.HALF_UP四舍五入的截取方式。 事实上 BigDecimal除了外RoundingMode.HALF_UP的舍入方式外,还有如下的截取方式: RoundingMode.HALF_UP:四舍五入,向上舍入。RoundingMode.HALF_DOWN:四舍五入,向下舍入。RoundingMode.HALF_EVEN:四舍五入,如果舍弃部分等于0.5,则舍入到最接近的偶数。?根据业务需要,合理的使用compareTo和equals?由于BigDecimal 内部对 equals方法逻辑进行了重写,这使得equals方法不仅比较数值部分,还比较标度。因此只有数值和标度都相同时equals 方法才会返回 true。例如: publicclassBigDecimalComparison{ publicstaticvoidmain(String[]args){ BigDecimalbd1=newBigDecimal("1.0"); BigDecimalbd2=newBigDecimal("1.00"); System.out.println(bd1.equals(bd2));//输出false } } 在这个例子中,bd1 = 1.0 bd2 = 1.00 两个数的数值部分代表的含义是完全相同的,但其精度却不同,此时如果使用equals 方法进行比较,则会返回 false。如果贸然使用equals 是很容易导致出现意料之外的结果。 为了保证数值的比较,BigDecimal 内部也对compareTo 方法进行了重写,使得compareTo方法只比较BigDecimal的数值部分而不考虑标度。「因此如果两个 BigDecimal对象的数值相等,即使标度不同compareTo 方法也会认为它们相等。」 publicclassBigDecimalComparison{ publicstaticvoidmain(String[]args){ BigDecimalbd1=newBigDecimal("1.0"); BigDecimalbd2=newBigDecimal("1.00"); System.out.println(bd1.compareTo(bd2));// } } 在这个例子中最终的输出结果为0,即代表bd1和bd2相等。这主要是因为bd1和bd2的数值相等因此compareTo 方法返回 0。 因此对于为了避免不必要的混淆和错误,尽量遵循以下最佳实践: 「明确比较目的」:在使用 BigDecimal 进行比较时,首先明确你的比较目的是检查数值相等还是完全相等(包括标度)。如果仅比较数值,请使用 compareTo 方法。如果需要完全相等,请使用 equals 方法。「避免误解」:理解 BigDecimal 的 equals 方法会考虑标度,而 compareTo 方法只比较数值。在常见的数值比较中,更推荐使用 compareTo 方法。?慎用BigDecimal的toString方法?BigDecimal内部对toString方法进行重载,这使得BigDecimal 的 toString 方法会自动去除尾随零,并且使用科学计数法表示非常大的或非常小的数值。例如: publicclassBigDecimalToStringExample{ publicstaticvoidmain(String[]args){ BigDecimalbd1=newBigDecimal("123.4500"); BigDecimalbd2=newBigDecimal("0.00012345"); System.out.println(bd1.toString()); System.out.println(bd2.toString()); } } 上述代码分别会输出123.45、1.2345E-4。其中bd1 的尾随零被去除,而 bd2 使用了科学计数法进行数据的表示。而为了避免这类问题的发生,可以使用 BigDecimal 的 toPlainString 方法。该方法不会去除尾随零,也不会使用科学计数法。 importjava.math.BigDecimal; publicclassBigDecimalToPlainStringExample{ publicstaticvoidmain(String[]args){ BigDecimalbd1=newBigDecimal("123.4500"); BigDecimalbd2=newBigDecimal("0.00012345"); System.out.println(bd1.toPlainString());//输出123.4500 System.out.println(bd2.toPlainString());//输出0.00012345 } } 不难看出,在这个例子中toPlainString 方法保留了尾随零,并且没有使用科学计数法,输出格式更加直观。 「toString 方法」:自动去除尾随零,使用科学计数法表示非常大或非常小的数值。可能导致格式不符合预期。「toPlainString 方法」:不会去除尾随零,不会使用科学计数法,适合需要保留原始数值格式的场景。总结本文主要对BigDecimal内部对于浮点数的存储规则进行分析,以加深读者对于BigDecimal的理解。同时整理了如下五条BigDecimal使用的最佳实践: 为了避免精度丢失,尽量使用BigDecimal(String val)构造方法或者BigDecimal.valueOf(double val);使用 BigDecimal进行除法运算时,指明数据结果的精度;根据业务需要,合理的使用compareTo和equals;慎用BigDecimal的toString方法。 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2019-10-18_王海峰领衔百度飞桨,汇聚150万开发者的AI平台正成为行业标杆 下一篇:2025-04-24_被《经验时代》刷屏之后,剑桥博士长文讲述RL破局之路

TAG标签:

17
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价