浮点数精度问题: 用实际生产环境一个发现的BUG 为例,进行记录;
01-问题背景
合作方对价格字段 要求 .00 标准格式 ; 也就是2位小数点;
因此我方对接开发代码对价格字段专门写一个方法,目的就是输出 保留两位小数的字符串
方法明细:
package org.example;
import java.math.BigDecimal;
import java.text.DecimalFormat;
/**
* Author: kaka@bug.com
* Date: 2023/12/8-18:40
* ---------------------------------------
* Desc: 输出保留两位小数的字符串
*/
public class Test {
public static void main(String[] args) throws Exception {
double p1 = 235.6;
String pr1 = getBookPriceToString(p1);
double p2 = 157.7;
String pr2 = getBookPriceToString(p2);
System.out.println(pr1); // 输出235.60
System.out.println(pr2); // 输出157.7
}
// 总的来说,这段代码的目的是为了保证价格的字符串形式总是有两位小数,如果原始价格的小数部分不足两位,则会进行补"0"的操作。
public static String getBookPriceToString(Double buyPrice) throws Exception {
Double buPrice1 = buyPrice * 100;
Integer roundNumber = buPrice1.intValue();
String buyPriceStr = buyPrice.toString();
if (roundNumber % 10 > 0) {
return buyPriceStr;
}
if (roundNumber % 100 > 0) {
return buyPriceStr + "0";
}
return buyPrice.intValue() + ".00";
}
/*
* 这段代码的主要功能是将传入的buyPrice(购买价格)转换为字符串格式,并根据其小数部分的情况进行不同的处理。
Double buPrice1 = buyPrice * 100;:将buyPrice乘以100,目的是为了后续判断价格的小数部分是否存在。
Integer roundNumber = buPrice1.intValue();:将乘以100后的价格转换为整数,这样就可以通过取余的方式来判断小数部分的情况。
String buyPriceStr = buyPrice.toString();:将buyPrice转换为字符串,为后续的返回做准备。
if (roundNumber % 10 > 0) { return buyPriceStr; }:如果价格的小数部分的十分位大于0(即价格的小数部分不是0.X0的形式),则直接返回原始价格的字符串形式。
if (roundNumber % 100 > 0) { return buyPriceStr + "0"; }:如果价格的小数部分的百分位大于0(即价格的小数部分是0.0X的形式),则在原始价格的字符串形式后面加上"0"。
return buyPrice.intValue() + ".00";:如果价格的小数部分为0(即价格为整数),则在价格后面加上".00"。
总的来说,这段代码的目的是为了保证价格的字符串形式总是有两位小数,如果原始价格的小数部分不足两位,则会进行补"0"的操作。
* */
}
当输入157.7时候, debug运行计算时候的过程如下图:
02-改善方法
package org.example;
import java.math.BigDecimal;
import java.text.DecimalFormat;
/**
* Author: method1
* Date: 2023/12/8-18:40
* ---------------------------------------
* Desc: 描述该类的作用
*/
public class Test {
public static void main(String[] args) throws Exception {
double p1 = 235.6;
String pr1 = getBookPriceToString(p1);
double p2 = 157.7;
String pr2 = getBookPriceToString(p2);
System.out.println(pr1); // 输出235.60
System.out.println(pr2); // 输出157.7
System.out.println(getBookPriceToStringNew(235.6)); // 输出235.60
System.out.println(getBookPriceToStringNew(157.7)); // 输出157.70
}
// 总的来说,这段代码的目的是为了保证价格的字符串形式总是有两位小数,如果原始价格的小数部分不足两位,则会进行补"0"的操作。
public static String getBookPriceToString(Double buyPrice) throws Exception {
Double buPrice1 = buyPrice * 100;
Integer roundNumber = buPrice1.intValue();
String buyPriceStr = buyPrice.toString();
if (roundNumber % 10 > 0) {
return buyPriceStr;
}
if (roundNumber % 100 > 0) {
return buyPriceStr + "0";
}
return buyPrice.intValue() + ".00";
}
// 优化后 方法的目的应该是:输出保留两位小数的字符串
public static String getBookPriceToStringNew(Double buyPrice) throws Exception {
DecimalFormat df = new DecimalFormat("0.00");
return df.format(buyPrice);
}
}
package org.example;
import java.math.BigDecimal;
import java.text.DecimalFormat;
/**
* Author: method2
* Date: 2023/12/8-18:40
* ---------------------------------------
* Desc:
*/
public class Test {
public static void main(String[] args) throws Exception {
double p1 = 235.6;
String pr1 = getBookPriceToString(p1);
double p2 = 157.7;
String pr2 = getBookPriceToString(p2);
System.out.println(pr1); // 输出235.60
System.out.println(pr2); // 输出157.7
System.out.println(getBookPriceToStringNew(235.6)); // 输出235.60
System.out.println(getBookPriceToStringNew(157.7)); // 输出157.70
System.out.println(getBookPriceToString2(157.7)); // 输出157.70
System.out.println(getBookPriceToString3(157.7)); // 157.7
}
// 目的是为了保证价格的字符串形式总有两位小数,如果原始价格的小数部分不足两位,则会进行补"0"的操作。
public static String getBookPriceToString(Double buyPrice) throws Exception {
Double buPrice1 = buyPrice * 100;
Integer roundNumber = buPrice1.intValue();
String buyPriceStr = buyPrice.toString();
if (roundNumber % 10 > 0) {
return buyPriceStr;
}
if (roundNumber % 100 > 0) {
return buyPriceStr + "0";
}
return buyPrice.intValue() + ".00";
}
public static String getBookPriceToString2(Double buyPrice) throws Exception {
if (buyPrice == null) {
throw new Exception("buyPrice cannot be null");
}
if (buyPrice <= 0) {
throw new Exception("buyPrice must be greater than 0");
}
DecimalFormat df = new DecimalFormat("#.00");
return df.format(buyPrice);
}
// 优化后 方法的目的应该是:输出保留两位小数的字符串
public static String getBookPriceToStringNew(Double buyPrice) throws Exception {
DecimalFormat df = new DecimalFormat("0.00");
return df.format(buyPrice);
}
// 实际上线代码
public static String getBookPriceToString3(Double buyPrice) throws Exception {
BigDecimal price = BigDecimal.valueOf(buyPrice);
DecimalFormat df = new DecimalFormat("#.00");
return df.format(price);
}
}
综上所述
浮点数精度问题 可以通过 BigDecimal 或者 DecimalFormat 来解决;