BigDecimal 的那些坑事儿

本文详细介绍了在Java中使用BigDecimal类解决浮点数转整数时因精度损失而导致的问题,包括如何正确进行元转分的代码实现,以及通过MathContext对象调整有效位数的方法,确保输出结果的准确性。

最近查看rebate数据时,发现一个bug,主要现象是,当扣款支付宝的账号款项时,返回的是数字的金额为元,而数据库把金额存储为分,这中间要做元与分的转化,这个转化规则很简单,就是*100的,所以一开始代码很简单,如下。

Float f =  Float.valueOf(s);
f =f*100;
Long result = f.longValue();
s=”9.86” 时,杯具出现了, result 的结果为 985 而不是 986 float 的精度损失导致 float(985.99994) 转化为整形时,丢掉小数部分成为 985 ,简单的方法,我们可以提高精度使用双精度的 double类型,提高精度,比如
Double d =  Double.valueOf(s);
d = d*100;
Long result = d.longValue();
当s=”9.86”时,确实能够得到正确结果,但是当s=”1219.86”时,这时候由于精度问题导致最终的result为121985为不是121986。当时以为使用double解决的问题,其实隐藏更隐蔽的bug。
针对这样的问题,如果使用C/C++语言,那么通用解决方案可以这样。

Double d =  Double.valueOf(s);
d = d*100+0.5;// 注意这里,我们使用的是+0.5的形式。
Long result = d.longValue();
但是,我们使用的java语言,java应该有更优雅的解决方案,那就是BigDecimal。

使用BigDecimal的解决方案成这个样子

Double dd= Double.valueOf(s);
BigDecimal bigD = new BigDecimal(dd);
bigD = bigD.multiply(new BigDecimal(100));
Long result = bigD.longValue();
狂晕,输出结果是985为不是986,打印bigD
System.out.println(bigD.toString());
输出如下
985.9999999999999431565811391919851303100585937500
不会再加上一个BigDecimal(0.5)吧。我相信在使用过BigDecimal过程中,肯定有那里不对的地方, multiply 方法中可以传入精度,那就构造 MathContext 对象,修改如下

Double dd= Double.valueOf(s);
BigDecimal bigD = new BigDecimal(dd);
MathContextmc = new MathContext(4,RoundingMode.HALF_UP);
//4表示取四位有效数字,RoundingMode.HALF_UP表示四舍五入
bigD= bigD.multiply(new BigDecimal(100),mc);
Long result = bigD.longValue();
最后结果输出为986,貌似已经找到完成解决方案,其实不然,注意到 MathContext中的4了嘛?这是因为我们保留4位有效数字,假如我们输入的数字是大于4的,比如1219.86,最终输出结果是122000,这是因为1219.86保留4位有效数字时,第四位的9四舍五入,除去精确位补零,所以最终结果成了122000。问题就成了,我们必须知道元变分后的最终有效位数,”9.86”,有效位数是4,”19.86”有效位数是5,把字符串s的长度传过去就可以了,那么代码如下
Double dd =Double.valueOf(s);
BigDecimalbigD = new BigDecimal(dd);
MathContextmc = new MathContext(s.length(),RoundingMode.HALF_UP);
//4表示取四位有效数字,RoundingMode.HALF_UP表示四舍五入
bigD= bigD.multiply(new BigDecimal(100),mc);
Long result = bigD.longValue();
至此,已经可以得到一个正确的元转分的代码,但是这里的s.length()终归不让人感觉舒服,接下来,我们探索BigDecimal原理,尝试用更优雅的方法解决这个问题。
BigDecimal,不可变的、任意精度的有符号十进制数。 BigDecimal 由任意精度的整数 非标度值 和 32 位的整数 标度(scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。因此, BigDecimal 表示的数值是 (unscaledValue × 10-scale)。我们知道BigDecimal有三个主要的构造函数

1

public BigDecimal(double val)

将double表示形式转换为BigDecimal

2

public BigDecimal(int val)

将int表示形式转换为BigDecimal

3

public BigDecimal(String val)

将字符串表示形式转换为BigDecimal

通过这三个构造函数,可以把double类型,int类型,String类型构造为BigDecimal对象,在BigDecimal对象内通过BigIntegerintVal存储传递对象数字部分,通过int scale;记录小数点位数,通过int precision;记录有效位数(默认为0)。
BigDecimal的加减乘除就成了BigInteger与BigInteger之间的加减乘除,浮点数的计算也转化为整形的计算,可以大大提供性能,并且通过BigInteger可以保存大数字,从而实现真正大十进制的计算,在整个计算过程中,还涉及scale的判断和precision判断从而确定最终输出结果。
我们先看一个例子

BigDecimal d1 = new BigDecimal(0.6);
BigDecimal d2 = new BigDecimal(0.4);
BigDecimal d3 = d1.divide(d2);
System.out.println(d3);
大家猜一下,以上输出结果是? 再接着看下面的代码
BigDecimal d1 = new BigDecimal(“0.6”);
BigDecimal d2 = new BigDecimal(“0.4”);
BigDecimal d3 = d1.divide(d2);
System.out.println(d3);
看似相似的代码,其结果完全不同,第一个例子中,抛出异常。第二个例子中,输出打印结果为 1.5 。造成这种差异的主要原因是第一个例子中的创建 BigDecimal 时, 0.6 0.4 是浮动类型的,浮点型放入 BigDecimal 内,其存储值为
0.59999999999999997779553950749686919152736663818359375
0.40000000000000002220446049250313080847263336181640625
这两个浮点数相除时,由于除不尽,而又没有设置精度和保留小数点位数,导致抛出异常。而第二个例子中 0.6 0.4 是字符串类型,由于 BigDecimal 存储特性,通过BigInteger记录BigDecimal的值,所以,0.6和0.4可以非常正确的记录为
0.6
0.4
两者相除得出1.5来。
对于第一个例子,如果我们想得到正确结果,可以这样来
BigDecimal d1 = new BigDecimal(0.6);
BigDecimal d2 = new BigDecimal(0.4);
BigDecimal d3 = d1.divide(d2, 1, BigDecimal.ROUND_HALF_UP);
现在看我们留下的那个问题,使用更优雅的方式解决元转化为分的方式,上一个问题中,我们通过传递s.length()从而获得精度,如果之前的s是double类型的,那边这样的方式就会有问题,通过上面的例子,我们可以调整为一下的通用方式
Double dd= Double.valueOf(s);
BigDecimal bigD = new BigDecimal(dd);
bigD = bigD.multiply(newBigDecimal(100)). divide(1, 1, BigDecimal.ROUND_HALF_UP);
Long result = bigD.longValue();
我们通过/1,然后设置保留小数点方式,以及设置数字保留模式,从而得到两个数乘积的小数部分。还有以下模式

枚举常量摘要 
ROUND_CEILING  
          向正无限大方向舍入的舍入模式。
ROUND_DOWN  
          向零方向舍入的舍入模式。
ROUND_FLOOR  
          向负无限大方向舍入的舍入模式。
ROUND_HALF_DOWN  
          向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。
ROUND_HALF_EVEN  
          向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
ROUND_HALF_UP  
          向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。
ROUND_UNNECESSARY  
          用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。(默认模式
ROUND_UP  
          远离零方向舍入的舍入模式。

总结:
1:尽量避免传递double类型,有可能话,尽量使用intString类型。
2:做乘除计算时,一定要设置精度和保留小数点位数。
3BigDecimal计算时,单独放到try catch内。

参考资料
IEEE 754简介: http://baike.baidu.com/view/1698149.htm
IEEE 754官方协议:http://grouper.ieee.org/groups/754/
BigDecimal函数列表:http://hi.baidu.com/logan9999/item/eeaea014677323fd9c778abd
浮点数与IEEE 754: http://www.cnblogs.com/kingwolfofsky/archive/2011/07/21/2112299.html
MathContext:http://doc.java.sun.com/DocWeb/api/all/java.math.MathContext
BigDecimal:http://doc.java.sun.com/DocWeb/api/all/java.math.BigDecimal

【重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: 本资源原本已设置为“0积分下载”,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,自动将部分资源的积分调整为非0数值(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、2、3……),请谨慎决定是否下载。 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。强烈建议:仅在页面显示为0积分时进行下载。 另外,本资源描述中并未直接提供具体的下载地址或外部链接,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝学习进步!
【重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: 本资源原本已设置为“0积分下载”,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,自动将部分资源的积分调整为非0数值(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、2、3……),请谨慎决定是否下载。 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。强烈建议:仅在页面显示为0积分时进行下载。 另外,本资源描述中并未直接提供具体的下载地址或外部链接,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝学习进步!
内容概要:本文详细介绍了一种基于动态时间规整(DTW)距离与Kmeans聚类算法相结合的时间序列聚类分析模型,并提供了完整的Matlab代码实现。该模型通过引入DTW距离度量方式,有效解决了传统欧氏距离在处理长度不一或存在时间偏移的时间序列数据时的局限性,能够更准确地捕捉序列间的形状相似性。结合Kmeans算法,实现了对具有相似变化趋势的时间序列进行高效聚类,适用于电力负荷、气象数据、金融走势等领域中的模式识别与数据挖掘任务。文中系统阐述了算法原理、关键步骤、代码结构及参数设置,便于用户理解、复现与拓展应用。; 适合人群:具备一定Matlab编程基础,从事数据分析、信号处理、电力系统、自动化、气象预测、金融工程等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①对非等长时间序列或存在时间错位的数据进行聚类分析;②研究DTW与传统聚类算法融合的技术路径;③应用于负荷曲线分类、设备运行状态识别、金融市场走势划分、气候模式聚类、生物信号分析等实际科研与工程项目中的特征提取与数据降维任务。; 阅读建议:建议结合所提供的Matlab代码进行动手实践,重点理解DTW距离矩阵的构建过程以及Kmeans在非欧式空间中的迭代优化机制,可通过加载真实数据集验证模型性能,并尝试将其扩展至其他聚类算法(如层次聚类、谱聚类)以比较效果差异。
内容概要:本文详细介绍了“谐波含量闭环调控的光储VSG并网自适应虚拟谐波阻抗策略”的Simulink仿真实现方法,聚焦于光伏发电与储能系统结合虚拟同步发电机(VSG)并网的技术背景,提出了一种能够动态调节谐波阻抗的自适应控制策略,旨在提升并网电能质量与系统稳定性。研究深入剖析了谐波闭环调控机制与虚拟阻抗自适应调整算法的设计原理,并通过构建完整的Simulink仿真模型进行验证,展示了在不同运行工况下有效抑制谐波电流、优化并网性能的能力。该策略对于高比例新能源接入条件下电网谐波治理和系统稳定运行具有重要的理论价值与工程应用前景。; 适合人群:具备电力电子、新能源并网、自动控制等相关专业知识,从事电气工程、能源系统仿真与控制领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①用于研究光储系统并网过程中的谐波抑制与电能质量问题;②为VSG并网控制策略的优化设计提供可靠的仿真验证平台;③适用于新型电力系统中自适应控制算法开发与电能质量综合治理技术的研究; 阅读建议:建议结合Simulink仿真模型与技术文档同步学习,重点关注控制逻辑实现细节与关键参数整定方法,可进一步拓展至多机并联、弱电网等复杂场景下的适应性分析,需具备MATLAB/Simulink基本操作能力。
内容概要:本文系统阐述了基于多面体聚合方法(包括内近似模型与闵可夫斯基和)对大规模电动汽车群体的可调能力(即功率容量)进行量化评估的研究,并配套提供了完整的Matlab代码实现。该方法通过构建单辆电动汽车充放电行为的多面体灵活性模型,利用内近似策略降低计算复杂度,并通过闵可夫斯基和运算实现对海量个体灵活性集合的聚合,从而精确刻画电动汽车集群在不同时间尺度下的总体可控功率边界。研究重点在于平衡模型精度与计算效率,确保所提方法适用于实际电力系统中对电动汽车作为灵活资源的调度潜力分析,为电网侧的需求响应、辅助服务参与及新型电力系统规划提供理论支撑与实用工具。; 适合人群:电力系统、智能电网、电动汽车与电网互动(V2G)、可再生能源集成等领域的科研人员、高校研究生及工程技术人员,需具备良好的凸几何、集合论基础知识以及Matlab编程与数值计算能力。; 使用场景及目标:①评估大规模电动汽车集群在日内或日前尺度下的可调度功率范围,支撑电网公司制定削峰填谷、频率调节等调度策略;②为电力市场中的需求响应项目、辅助服务产品设计提供量化依据;③服务于城市级充电基础设施规划与主动配电网的灵活性提升研究,推动电动汽车从用电负荷向移动式储能资源的角色转变。; 阅读建议:建议读者深入研读内近似模型的数学推导过程,理解其在保证保守性前提下提升计算可行性的优势,并结合所提供的Matlab代码,动手实现从小规模算例到大规模场景的仿真验证,重点关注聚合后灵活性域的几何特征演变及算法性能随车辆数量增长的变化趋势。
内容概要:本文针对计及风电不确定性的电力系统黑启动与负荷恢复协同优化问题展开研究,提出了一种融合风电出力随机性与波动性的不确定性优化模型,并基于Matlab平台实现了算法仿真与验证。研究聚焦于电力系统在大面积停电后的恢复过程中,如何充分利用风电等可再生能源作为黑启动电源,科学规划机组启动顺序、黑启动路径以及关键负荷的恢复时序,从而提升系统恢复的效率、安全性与经济性。通过引入场景生成与削减技术处理风电不确定性,结合智能优化算法求解多阶段、多约束的协同优化问题,实现了对恢复过程的精细化决策支持。; 适合人群:具备电力系统分析、随机优化理论基础及Matlab编程能力的研究生、高校科研人员以及电力公司从事系统运行与应急管理的技术人员。; 使用场景及目标:①为含高比例新能源的新型电力系统制定黑启动预案提供理论依据与量化分析工具;②支撑极端灾害下电网恢复策略的仿真评估与优化设计;③深化对新能源参与系统恢复机理的理解,推动电力系统韧性能力的提升。; 阅读建议:建议读者结合Matlab代码深入理解模型构建、不确定性处理及算法求解全过程,重点关注场景概率建模、目标函数构建与多约束条件的处理方法,可进一步将该框架拓展应用于其他复杂电力系统恢复与优化问题的研究中。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值