在网络上,有一个长期争论不休的话题:在金额计算中,是应该使用Long还是BigDecimal?这个话题引发了激烈的讨论。有人认为使用BigDecimal是常识。
有一些人支持使用Long,将金额的单位设计为分,然后乘以100,使用Long进行存储和计算,这样就不用担心小数点问题。
此外,一些银行系统也选择使用Long。
还有一种方法是使用String,这是一种非常灵活的方式。
有人认为成年人不应该只做选择题,而是应该同时使用Long和BigDecimal。
另一种方法是封装一个金额的基类,对金额进行统一处理。
排除float和double
当涉及到金额时,首先要排除的是float和double。它们不适合用于精确的金融计算,因为float和double是基于IEEE 754标准的浮点数表示,无法精确地表示所有的十进制小数。这可能导致在进行财务计算时出现舍入误差,这些误差可能会累积并导致不可预测的结果。
关于带精度的计算,我们不推荐使用float以及double,推荐使用BigDecimal,具体原因请参考:聊一聊_BigDecimal_使用时的陷阱
选择Long
Long类型在Java中用于存储64位整数。它的主要优点是速度快,因为整数运算在CPU层面是非常高效的。另外,Long类型也占用较少的内存,并且整数类型(BIGINT)在数据库中占用较少的存储空间。
然而,使用Long类型在处理金额时存在明显的缺点:
- 精度问题: Long只能存储整数,无法直接表示小数。使用Long来表示以分为单位的金额(例如,100表示1元),会失去小数的精度。即使使用某种方式来表示小数(例如,乘以100或10000),也会遇到舍入误差的问题。并且这种计算方式也会增加计算的复杂度。
- 浮点数问题: 虽然这不是直接使用Long的问题,但如果尝试将Long与浮点数(如double或float)进行转换以进行计算(比如汇率计算等),还是会遇到浮点数精度问题,这可能导致在财务计算中出现不可接受的误差。
在阿里巴巴的开发手册中建议使用Long。
然而,在一些金融系统中,对小数位要求比较高,比如精确到小数点后6位,那么使用Long进行存储,每次在计算时都要除以或者乘以1000000,那么计算的开销就很大了。
并且,如果在需求确认时,我们无法知道金额要求的小数位,那么使用Long也是不行的,我们并不知道需要乘以或者除以多少个0。
选择BigDecimal
BigDecimal是Java提供的一个类,用于任意精度的算术运算。它的主要优点是提供了高精度的计算,这对于金融和货币计算来说是非常重要的。BigDecimal可以表示任意大小的正数、负数或零,并可以精确控制舍入行为。并且在数据库中存储时也有对应的类型进行匹配,比如MySQL的DECIMAL类型提供了精确的数值存储,可以匹配BigDecimal的精度。
然而,BigDecimal也有一些缺点:
- 性能: 与Long相比,BigDecimal的性能较差。因为它的运算需要更多的内存和CPU时间。
- 复杂性: 使用BigDecimal进行运算比使用Long或基本数据类型更复杂。你需要考虑舍入模式、精度等因素。
- 在数据库中需要更多的存储空间来存储小数部分。
而在Mysql的开发手册中,建议金额需要进行小数位计算时,存储要使用Decimal,否则我们要将金额乘以对应小数位的倍数变成BIGINT进行存储。
总结
基于上述对Long和BigDecimal的优缺点分析,我们可以得出以下结论:
在金额计算层面,即代码实现中,推荐使用BigDecimal进行所有与金额相关的计算。BigDecimal提供了高精度的数值运算,能够确保金额计算的精确性,避免了因浮点数精度问题导致的财务误差。使用BigDecimal可以简化代码逻辑,减少因处理精度问题而引入的复杂性。
而在数据库存储方面,我们需要根据具体需求进行权衡。如果业务需求已经明确金额只需精确到分(如某些国家/地区的货币最小单位为分),并且我们确信不会涉及到需要更高精度的小数计算,那么可以使用Long类型进行存储,将金额转换为最小货币单位(如分)进行存储。这样可以节省存储空间并提高查询性能。
但是如果业务需求中金额的小数位数不确定,或者可能涉及多位小数的计算(如国际货币交易等),那么最好使用DECIMAL或NUMERIC类型进行存储。这些类型提供了精确的数值存储,可以确保数据库中的数据与应用程序中的BigDecimal对象保持一致,避免数据转换过程中可能引入的精度损失。
本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等