java中除金额_JAVA中如何处理金额类数据

本文探讨了JAVA中处理金额类数据时遇到的精度问题,通过实例展示了float和double类型的不足,并推荐使用BigDecimal进行精确计算。同时,介绍了BigDecimal的构造方法和常见操作,以及在数据库中保存金额数据的两种方式:decimal和long。最后,建议在跨服务传输金额数据时使用String类型。
摘要由CSDN通过智能技术生成

由闲鱼的转账BUG引起的思考

想写这一篇文章主要是前两天看到了一个闲鱼的BUG,而且自己也复现了这个问题。在使用闲鱼的时候当我向好友转账2.1的时候,最终支付宝显示的却只有2.09(前天出现的问题,目前闲鱼已经修复了这个问题)。作为一个消费者这只是一个BUG,但是作为一个JAVA开发,就让我思考到假如这个金额的数据是需要服务端进行处理?JAVA要如何处理这些数据。

1b106eeffb998367e1f61ea98dbd0b29.png

我的一分钱呢??????cca3cef5a3e40bd5d8ac8aa0d728f4dc.png

6d701114151406504d4e3a9aedcbaa16.png

这从未设想的问题啊,所以这里就整理下对于服务端 的开发,对于金额的处理、储存和传输应该如何操作

JAVA对金额数据的处理

错误的付款金额

在设计商品的数据结构时候,我们可能尝试将商品的价格设置为float或者double类型,而购买数量因为产品不同可能被设计为int或者long。当需要我们计算总价的时候,如果我们直接将单价*购买数量就会出现下面的情况:

public static void main(String[] args) {

float a = 72.49f;

System.out.println("商品a单价:" + a);

int n = 10;

System.out.println("购买a数量:" + n);

System.out.println("商品a总价:" + a*n);

double d1 = 0.58D;

long n1 = 100L;

System.out.println("商品b单价:" + d1);

System.out.println("购买b数量:" + n1);

System.out.println("商品b总价:" + d1*n1);

}

上面代码看起来就是很简单的乘法运算,但是投入到生产中会出现很大的问题,它会得到下面的结果

商品a单价:72.49

购买a数量:10

商品a总价:724.89996

商品b单价:0.58

购买b数量:100

商品b总价:57.99999999999999

可以看到本来应该支付724.9元的订单只需要支付724.89。而本来需要58块的订单缺只需要57.99。无论是float还是double都出现了金额缺失的情况。

使用BigDecimal进行金额计算

因为float和double存在精度丢失问题所以在进行数字的精确计算的时候,我们需要通过BigDecmal来进行精确计算。

将数字转换为BigDecimal

BigDecimal提供了相当多的构造方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NeOuTEcX-1593868618419)(BCC0E85C61C04C38A0C7D2A4C3AD8F5F)]

上面方法虽然多但是我们常用的构造方法就是下面几种,通过下面的方法来将String、int和long类型的数据转换为BigDecimal

// BigDecimal(int) 创建一个具有参数所指定整数值的对象

BigDecimal num1 = new BigDecimal(10);

// BigDecimal(long) 创建一个具有参数所指定长整数值的对象。

BigDecimal num2 = new BigDecimal(1000000L);

//BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象

BigDecimal num12 = new BigDecimal("0.005");

对于double和float的特殊处理

如果我们将double或者float数据使用上面方式获取BigDecimal则会得到下面这种错误结果

// 输出:72.48999786376953125

float a = 72.49f;

BigDecimal num3 = new BigDecimal(a);

System.out.println(num3);

// 输出:0.57999999999999996003197111349436454474925994873046875

double d1 = 0.58D;

BigDecimal num4 = new BigDecimal(d1);

System.out.println(num4);

有些文章中介绍可以使用其静态方法BigDecimal.valueOf(d1),但是此方法面对float的数据类型依旧无法准确输出内容。所以对于float我们最好将其转换为String后进行处理

float a = 72.49f;

String s = String.valueOf(a);

System.out.println(s);

对BigDecimal 数据进行操作

BigDecimal提供了一系列的方法让我们更加精确的对数据进行处理

方法

作用

例子

解释

add

加法

num1.add(num1和num2相加)

num1和num2相加

subtract

减法

num1.subtract(num2)

num1减去num2

multiply

乘法

num1.multiply(num2)

num1乘 num2

divide

除法

num2.divide(num1,2,BigDecimal.ROUND_HALF_UP)

num2 除以 num1,并且保留两位小数

divideToIntegralValue

除法并获取其整数部分

num2.divideToIntegralValue(num1)

num2 除以 num1,并获取其整数部分

compareTo

比较大小

num1.compareTo(num2)

num1和num2比大小,如果num1小于num2则返回-1,相等则返回0,大于则返回1

abs

绝对值

num3.abs()

返回num3的绝对值

特别需要注意!进行相关操作后并不会作用到原始数据上

在BigDecimal数据进行上面操作后并不会影响其原始数据的值,下面的操作中最终会存在三个不一样的值,原始的数据bigDecimal1、bigDecimal2计算后的结果add。

BigDecimal add = bigDecimal1.add(bigDecimal2);

System.out.println(add);

System.out.println(bigDecimal1);

System.out.println(bigDecimal2);

除法四舍五入操作

除法操作时调用的方法

public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode);

public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode);

其最后一个参数用来确定小数的舍入的策略。

数字参数

枚举参数

作用

BigDecimal.ROUND_UP

RoundingMode.UP

被舍弃的小数位如果不是0,则舍弃部分前面的数字+1

BigDecimal.ROUND_DOWN

RoundingMode.DOWN

不会对舍弃部分前面的数字+1

BigDecimal.ROUND_CEILING

RoundingMode.CEILING

如果结果是正数则使用RoundingMode.UP规则;如果结果是负数则使用RoundingMode.DOWN规则

BigDecimal.ROUND_FLOOR

RoundingMode.FLOOR

使用和RoundingMode.CEILING相反的策略

BigDecimal.ROUND_HALF_UP

RoundingMode.HALF_UP

可以理解为四舍五入

BigDecimal.ROUND_HALF_DOWN

RoundingMode.HALF_DOWN

舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同RoundingMode.DOWN

BigDecimal.ROUND_HALF_EVEN

RoundingMode.HALF_EVEN

如果距离相邻的数字相等,则向相邻的偶数舍入,如果不相等,则如果舍弃部分左边的数字为奇数,则舍入行为同RoundingMode.HALF_UP;如果为偶数,则舍入行为同RoundingMode.HALF_DOWN

BigDecimal.ROUND_UNNECESSARY

RoundingMode.UNNECESSARY

判断是否精确操作,如果需要进行舍入操作则抛出异常

金额类数据如何保存(MySQL)

在金额类数据处理完后,我们需要保存到数据库中,而对于这些交易数据,根据每个系统涉及交易的规模和业务不同,目前有三种选择(实际上只写了两种,网上有人介绍使用String或者说varchar,说实话我是不喜欢将金额存储为字符串)。

decimal

使用decimal在数据库中可以非常精确的表示一个数据的值,而一般保存交易金额我们可以将其类型设置为decimal(M,S),M表示整数和小数部分的总长度,S表示其中小数部分的位数,对于日常交易过程中我们所使用的的最小单位是分,也就是0.01元,所以可以设置为2;

long

有些设计中,将数据库中金额的单位认为是分。对于这种设计对于金额的数据类型可以设置为long,此时对于值为100的数据,会被认定为1元,而不是100元。

金额类数据如何传输

关于在通过跨服务跨系统进行金额数据传输的时候,数据类型如何确定,可以直接参照支付宝SDK上的要求使用String数据类型

个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容。假如我的这篇内容对你有任何帮助的话,麻烦给我点一个赞。你的点赞就是我前进的动力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值