最近在项目中数据的处理涉及到了金额,所以才查了这个,double平常使用可以但是不能用来计算金额,bigDecimal可以用来计算金额
但是我看项目中他们之前写的也是使用的double,所以我还是使用了double
如果对精度要求比较高的情况下还是使用bigDecimal比较好
java中的BigDecimal和double
1.double
1.1. 无法精确表示部分十进制小数
十进制小数转换为二进制时,部分数值会变成无限循环小数 ,而 double 只能存储有限位数的二进制,因此会截断并保留近似值。
最经典的例子:
java
double a = 0.1;
double b = 0.2;
System.out.println(a + b); // 输出 0.30000000000000004(而非预期的 0.3)
原因:0.1 转二进制是 0.0001100110011...(无限循环),double 只能存储其近似值,叠加后误差被放大。
1.2. 能精确表示的情况(特例)
-
整数:在
2⁵³范围内的整数(约 ±9×10¹⁵),double可以精确存储(例如25、20.0等)。例:
25 * 0.8 = 20.0,结果是整数,因此double能精确表示。 -
可被 2 的幂次整除的小数 :例如
0.5(二进制0.1)、0.25(二进制0.01)等。
1.3、适用场景与风险场景
适合使用 double 的场景
- 科学计算 / 工程计算:允许微小误差(如物理模型、图形渲染、数据统计等)。
- 非精确小数场景 :如温度(
23.5℃)、体重(62.3kg)等,对精度要求不高的数值。 - 需要大范围数值 :当数值范围超过整数类型(如
long的上限 ±9×10¹⁸),且可接受近似值时。
禁止使用 double 的场景(精度敏感)
- 金融 / 支付相关 :金额(如
100.05元)、税率、手续费等,误差可能导致资金损失或对账不平。 - 业务关键小数 :如商品单价、折扣率、库存数量(带小数,如
1.5件)等,需精确记录和计算。 - 数据库字段映射 :若数据库用
NUMERIC/DECIMAL(精确小数类型),实体类用double接收会导致精度丢失。
1.4、常见问题与解决方案
1.4.1. 问题:double 打印时显示很多小数位(如 0.1 显示为 0.1000000000000001)
-
原因:
double存储的是近似值,打印时会显示其真实存储的二进制对应的十进制值。 -
解决:用
String.format()或DecimalFormat格式化输出(仅用于展示,不改变存储的近似值):
java
double num = 0.1;
// 格式化保留 1 位小数,输出 0.1
System.out.println(String.format("%.1f", num));
1.4.2. 问题:double 比较大小(如 a == b 失效)
-
原因:近似值导致看似相等的数值,实际存储的二进制不同(如
0.1 + 0.2与0.3)。 -
解决:
-
用
BigDecimal比较(推荐):a.compareTo(b) == 0(相等返回 0)。 -
若必须用
double,则比较两者差值的绝对值是否小于极小值(如1e-9):
javadouble a = 0.1 + 0.2; double b = 0.3; // 差值小于 1e-9,认为相等 if (Math.abs(a - b) < 1e-9) { System.out.println("相等"); } -
1.5、总结
double 是一把 "双刃剑":它的优势是范围大、计算效率高,适合非精确场景;但精度丢失的特性使其在核心业务(尤其是金融)中完全不可用。
核心原则:
- 若业务允许微小误差 → 用
double; - 若要求绝对精确 → 必须用
BigDecimal+ 数据库NUMERIC类型。
2.BigDecimal
BigDecimal的内部存储基于两部分:
- 一个任意精度的整数 "unscaled value"(未缩放值)
- 一个 32 位整数 "scale"(缩放因子,表示小数点的位置)
例如:
- 当你输入
new BigDecimal("123.45")时,它会存储为 unscaled value=12345,scale=2 - 当你输入
new BigDecimal("123.4500")时,同样存储为 unscaled value=1234500,scale=4
这意味着:
- 如果你通过字符串构造
BigDecimal,它会精确保留字符串中的所有数字和小数点位置 - 如果你通过浮点数(如 double)构造,可能会因为浮点数本身的精度问题导致意外结果
BigDecimal会保留尾部的零,这一点与 double 不同
示例代码:
java
import java.math.BigDecimal;
public class BigDecimalExample {
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal("100.00");
BigDecimal bd2 = new BigDecimal("100");
BigDecimal bd3 = new BigDecimal(100.00); // 不推荐,可能有精度问题
System.out.println(bd1); // 输出 100.00
System.out.println(bd2); // 输出 100
System.out.println(bd3); // 可能输出 100.000000000000000...
}
}
所以,如果你希望精确控制BigDecimal的数字形式,最佳实践是:
- 使用字符串作为构造参数
- 需要时使用
setScale()方法显式设置精度 - 避免使用 double 作为构造参数
sql中的decimal,nuermic
在数据库中,DECIMAL 和 NUMERIC 是用于存储精确数值的类型,主要用于需要高精度计算的场景(如金融、货币、税务等),它们的特性和使用方式非常相似,但存在一些细微差别。
1. 基本特性
两者都是精确数值类型 ,与 FLOAT 或 DOUBLE(近似数值类型)不同,它们不会丢失精度,因为它们以字符串形式存储数值(而非二进制浮点数)。
定义格式通常为:
sql
DECIMAL(p, s)` 或 `NUMERIC(p, s)
p(precision):总位数(整数部分 + 小数部分),范围通常为 1~65(不同数据库可能有差异)。s(scale):小数部分的位数,范围为 0~p。
2. 主要区别
标准 SQL 中对两者的定义有细微差异:
NUMERIC(p, s):严格遵守p和s的限制,插入的值必须完全符合指定的精度和小数位数(超出则报错)。DECIMAL(p, s):可以接受比指定精度更高的值,但会自动截断或四舍五入到指定的s位小数(具体行为取决于数据库实现)。
但实际中,大多数数据库(如 MySQL、PostgreSQL、SQL Server)将两者视为同义词,处理方式完全一致,没有区别。例如:
- MySQL 文档明确说明
DECIMAL和NUMERIC是相同的。 - PostgreSQL 中两者行为一致,仅名称不同。
3. 使用场景
适合存储需要精确计算的数值,例如:
- 货币金额(如
DECIMAL(10, 2)表示最大 99999999.99)。 - 税率、利息等需要固定小数位的数值。
不适合存储不需要高精度的大数值(如科学计算中的近似值),此时用 FLOAT 更节省空间。
4. 与 Java 中 BigDecimal 的配合
数据库的 DECIMAL/NUMERIC 类型在 Java 中通常映射为 BigDecimal 类型,两者都是精确数值类型,配合使用可以避免精度丢失问题。
示例(JDBC 读取):
java
PreparedStatement ps = connection.prepareStatement("SELECT price FROM products WHERE id = ?");
ps.setInt(1, productId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
BigDecimal price = rs.getBigDecimal("price"); // 直接获取为BigDecimal
}
总结
DECIMAL和NUMERIC在大多数数据库中功能完全相同,都用于存储精确数值。- 核心是通过
(p, s)控制总位数和小数位数,确保数值精度。 - 与 Java 的
BigDecimal配合使用,是处理金融等高精度场景的最佳实践。