问题描述
在提交订单时候,输入充值金额和优惠码,后台会返回具体的订单信息,如下图,支付金额应该是1 * (1 - 0.09) = 0.91(这个是理想状态),但是表单显示的是0.90999997,
然后点击确认的时候,它会进行支付请求,最终报错如下,错误表达就是参数无效
解决过程
去支付宝沙箱官网看参数设置,主要是下面这五个参数
out_trade_no必选string(64)
【描述】商户订单号。
由商家自定义,64个字符以内,仅支持字母、数字、下划线且需保证在商户端不重复。
【示例值】20150320010101001total_amount必选price(11)
【描述】订单总金额,单位为元,精确到小数点后两位,取值范围为 [0.01,100000000]。金额不能为0。
【示例值】88.88
subject必选string(256)【描述】订单标题。
注意:不可使用特殊字符,如 /,=,& 等。
【示例值】Iphone6 16Gproduct_code必选string(64)
【描述】销售产品码,与支付宝签约的产品码名称。注:目前电脑支付场景下仅支持FAST_INSTANT_TRADE_PAY
【示例值】FAST_INSTANT_TRADE_PAYtime_expire可选string(32)
【描述】订单绝对超时时间。
格式为yyyy-MM-dd HH:mm:ss。超时时间范围:1m~15d。
注:time_expire和timeout_express两者只需传入一个或者都不传,两者均传入时,优先使用time_expire。
【示例值】2016-12-31 10:05:01
其中total_amount这个参数它要求精确到小数点后两位,再看我订单返回的值是0.90999997,就是这个参数导致支付失败
精度丢失说明:
浮点数精度丢失问题源于计算机如何表示和处理浮点数。计算机内部使用二进制(base-2)系统表示数字,而某些十进制小数无法被精确地表示为二进制小数。这导致在浮点数运算过程中出现精度丢失问题。
验证示例如下:
解决方法
使用BigDecimal
public OrderVo returnOrderInfo(Long uid, Long amount, String codes) {if(codes != null && (codes.length() != 49 || (codes.charAt(codes.length() - 1) != '0' && codes.charAt(codes.length() - 1) != '1'))){throw new RuntimeException("优惠码格式错误");}try{Float discountRate = null;BigDecimal realAmount = null;BigDecimal amounts = new BigDecimal(amount.toString());if(StringUtils.isNoneBlank(codes)){// 校验优惠码BaseResponse<PromotionVo> baseResponse = orderPromotionFeign.checkPromotionCode(uid, codes);if(baseResponse.getCode() != 0){throw new RuntimeException("优惠码不可用");}discountRate = baseResponse.getData().getDiscountRate();}if(discountRate == null){realAmount = amounts;}else {BigDecimal discount = new BigDecimal(discountRate.toString());realAmount = amounts.multiply(BigDecimal.ONE.subtract(discount));}realAmount = realAmount.setScale(2, BigDecimal.ROUND_HALF_UP);// 生成订单infoOrderVo orderVo = OrderVo.builder().subject("购买憨币").amount(amount).realAmount(realAmount.floatValue()).discountRate(discountRate).codes(codes).build();log.info("订单信息:{}", orderVo);return orderVo;}catch (Exception e){throw new RuntimeException("获取订单信息失败");}}
*注,在使用BigDecimal的时候,使用字符串构造 BigDecimal,不然还会引起精度问题,如下:
使用字符串构造 BigDecimal,就不会出现精度丢失问题:
总结:
在本文中,我分享了一个关于在线支付过程中遇到的精度丢失问题及其解决方案。用户在提交订单时,由于后台计算的支付金额存在精度问题,导致支付宝支付请求失败,并返回了INVALID_PARAMETER错误码。这个问题的根本原因在于浮点数在计算机中的表示方式,即二进制系统与十进制小数的不完全对应,这在金融计算中尤为关键。
为了解决这一问题,我提出了使用BigDecimal类来处理所有货币相关的计算。BigDecimal提供了更高精度的十进制运算能力,可以有效避免浮点数运算中的精度丢失。在示例代码中,我展示了如何使用BigDecimal进行精确的金额计算,并通过setScale方法设置小数点后两位,使用ROUND_HALF_UP参数进行四舍五入,以确保金额的准确性。
文章最后,指出了在使用BigDecimal时的最佳实践:应通过字符串构造器来创建BigDecimal实例,以避免直接使用数值构造时可能引起的精度问题。
总结来说,本文提供了一个实用的解决方案,帮助开发者在设计和实现涉及金钱的系统时,能够避免因浮点数精度问题导致的支付失败。通过使用BigDecimal,我们可以确保金额计算的准确性,从而提高系统的可靠性和用户的信任度。