MySQL的Double类型多少位会丢失精度?以及Java代码为什么会把Double转成科学计数法?以及怎么将科学计数法的数转成实际值(用BigDecimal遍历然后.toPlainString())
数值类型最大安全整数位数超出后行为DOUBLE15 位(保守)17 位(理论极限)第 16~17 位可能因舍入而变化第 18 位起必然失真💡金融、ID、精确计数等场景绝对不要用DOUBLE!必须用DECIMAL或整数类型。问题答案DOUBLE最多多少位不丢精度?15 位十进制有效数字(保守值),最多 17 位超过多少位会失真?第 16 位起可能失真,18 位起必然失真多少位会显示为科学计数法?不
在 MySQL 中,DOUBLE 类型的精度限制和科学计数法显示行为是两个不同层面的问题:
- 精度丢失 是由 IEEE 754 双精度浮点数的内部表示决定的(存储/计算层面)
- 科学计数法显示 是由 MySQL 客户端或应用层的格式化规则决定的(显示层面)
✅ 一、DOUBLE 的精度限制(何时会丢失精度?)
🔹 基本事实
DOUBLE遵循 IEEE 754 双精度标准- 使用 64 位(8 字节) 存储:
- 1 位符号位
- 11 位指数位
- 52 位尾数位(mantissa)
- 有效数字精度 ≈ 15~17 位十进制数字
📌 关键结论:超过 15~17 位有效数字的部分,一定会丢失精度。
🔹 示例说明
-- 创建测试表
CREATE TABLE test_double (val DOUBLE);
-- 插入一个 20 位整数
INSERT INTO test_double VALUES (12345678901234567890);
-- 查询结果
SELECT val FROM test_double;
-- 输出:1.2345678901234567e19 或 12345678901234567168
🔍 分析:
- 原始值:
12345678901234567890(20 位) - 实际存储:只能保留前 15~17 位有效数字
- 后几位被舍入或截断,变成
...7168或类似值 - 从第 16 位开始,数字不可信!
🔹 精度边界总结
| 数值类型 | 最大安全整数位数 | 超出后行为 |
|---|---|---|
DOUBLE |
15 位(保守) 17 位(理论极限) |
第 16~17 位可能因舍入而变化 第 18 位起必然失真 |
💡 金融、ID、精确计数等场景绝对不要用
DOUBLE!必须用DECIMAL或整数类型。
✅ 二、何时会自动转成科学计数法?(显示行为)
🔹 MySQL 服务器本身 不会主动转科学计数法
- MySQL 内部存储始终是二进制浮点数
- 是否显示为科学计数法,取决于客户端(如命令行、Navicat、JDBC、应用代码)
🔹 常见客户端行为
1. MySQL 命令行客户端(mysql CLI)
- 默认对 绝对值 ≥ 1e15 或 ≤ 1e-5 的数使用科学计数法
SELECT 1234567890123456; -- 显示为 1.234567890123456e15 SELECT 0.000001; -- 显示为 1e-6
2. JDBC / Java 应用
- 当
Double值很大时,toString()自动用科学计数法:System.out.println(1234567890123456.0); // 输出: 1.234567890123456E15 - 触发条件(Java 规则):
abs(x) >= 1e7(≥ 1000 万)abs(x) <= 1e-3(≤ 0.001)
3. 前端 JavaScript
- 对大于
1e21的数自动转科学计数法 - 但你的问题发生在后端,所以主要是 Java + JDBC 的行为
✅ 三、如何避免科学计数法显示?
方案 1:SQL 层强制转字符串(不推荐,治标不治本)
SELECT CAST(val AS CHAR) FROM test_double;
-- 但若 val 是 DOUBLE,仍可能返回 '1.234567890123456e15'
方案 2:Java 层用 BigDecimal.toPlainString()(✅ 推荐)
Object value = resultSet.getObject("val"); // 可能是 Double 或 BigDecimal
if (value instanceof Number) {
String plain = new BigDecimal(value.toString()).toPlainString();
// 输出: "12345678901234567168"(无科学计数法)
}
方案 3:根本解决 —— 改用 DECIMAL
ALTER TABLE your_table MODIFY COLUMN val DECIMAL(30,0);
DECIMAL永远不会自动转科学计数法- 存储和计算完全精确
✅ 总结表
| 问题 | 答案 |
|---|---|
DOUBLE 最多多少位不丢精度? |
15 位十进制有效数字(保守值),最多 17 位 |
| 超过多少位会失真? | 第 16 位起可能失真,18 位起必然失真 |
| 多少位会显示为科学计数法? | 不是 MySQL 决定的! • Java: ≥1e7 或 ≤1e-3 • MySQL CLI: ≥1e15 或 ≤1e-5 |
| 如何彻底避免? | 改用 DECIMAL(M,D) |
🔚 终极建议:
- 金额、数量、ID、统计值 → 用
DECIMAL- 科学计算、物理模拟 → 可用
DOUBLE,但接受精度损失
你当前的问题是:即使调用了 value.toString(),前端仍然收到科学计数法(如 "1.12e30")。
根本原因是:
SUM(zrb.recycle)返回的是Double或BigDecimal,而Double.toString()会自动使用科学计数法!
✅ 正确修复方式
你需要 将数值转为 BigDecimal,再调用 .toPlainString(),这是唯一能保证输出完整数字(无科学计数法)的方法。
🔧 修复的 Java 代码如下:
@Override
public ResponseEntity<?> getMonthlyDataTotal(MonthlySummaryDTO dto, Long currentUserId) {
User one = userService.findOne(currentUserId);
Long regionId = one.getRegionId();
StringBuilder sb = new StringBuilder();
Map<String, Object> params = new HashMap<>();
sb.append("SELECT zv.variety, ")
.append("COALESCE(temp.recycle, 0) AS recycle, ")
.append("COALESCE(temp.num, 0) AS num, ")
.append("COALESCE(temp.sales, 0) AS sales ")
.append("FROM zszy_variety zv LEFT JOIN (");
if (dto.getRegionId() != null && dto.getRegionId() != 0 && !dto.getRegionId().equals(regionId)) {
sb.append("SELECT zv.id, zv.variety, ")
.append("SUM(zrb.recycle) AS recycle, ")
.append("SUM(zrb.num) AS num, ")
.append("SUM(zrb.sales) AS sales ")
.append("FROM zszy_variety zv ")
.append("LEFT JOIN zszy_report_body zrb ON zv.id = zrb.variety_id ")
.append("LEFT JOIN zszy_report_head zrh ON zrb.head_id = zrh.id ")
.append("WHERE zrh.enter_name IN (")
.append(" SELECT asu.enter_name ")
.append(" FROM ar_sys_user asu ")
.append(" WHERE asu.user_type = 'ENTER' AND asu.region_id LIKE CONCAT(:regionId, '%') ")
.append(") AND zv.id NOT IN (18, 20, 21)");
params.put("regionId", dto.getRegionId());
} else {
sb.append("SELECT zv.id, zv.variety, ")
.append("SUM(zrb.recycle) AS recycle, ")
.append("SUM(zrb.num) AS num, ")
.append("SUM(zrb.sales) AS sales ")
.append("FROM zszy_variety zv ")
.append("LEFT JOIN zszy_report_body zrb ON zv.id = zrb.variety_id ")
.append("LEFT JOIN zszy_report_head zrh ON zrb.head_id = zrh.id ")
.append("WHERE zrh.enter_name IN (")
.append(" SELECT asu.enter_name ")
.append(" FROM ar_sys_user asu ")
.append(" WHERE asu.user_type = 'ENTER' AND asu.region_id LIKE CONCAT(")
.append(" (SELECT region_id FROM ar_sys_user WHERE id = :userId), '%'")
.append(" )")
.append(") AND zv.id NOT IN (18, 20, 21)");
params.put("userId", currentUserId);
}
// 时间范围过滤
if (StringUtils.isNotBlank(dto.getStartTime())) {
sb.append(" AND zrh.report_date >= :beginTime");
params.put("beginTime", dto.getStartTime());
}
if (StringUtils.isNotBlank(dto.getEndTime())) {
sb.append(" AND zrh.report_date <= :endTime");
params.put("endTime", dto.getEndTime());
}
if (StringUtils.isNotBlank(dto.getResourceName())) {
sb.append(" AND zv.variety LIKE CONCAT('%', :resourceName, '%')");
params.put("resourceName", dto.getResourceName());
}
sb.append(" GROUP BY zv.id, zv.variety ORDER BY zv.id) AS temp ON zv.id = temp.id");
SqlMaker script = new SqlMaker(SqlMaker.ScriptType.SQL);
script.setScript(sb.toString());
List<Map<String, Object>> maps = baseRepository.listAllBySql(script, params);
// ✅ 关键修复:使用 BigDecimal.toPlainString() 避免科学计数法
for (Map<String, Object> map : maps) {
for (String key : Arrays.asList("recycle", "num", "sales")) {
Object value = map.get(key);
if (value == null) {
map.put(key, "0");
} else if (value instanceof Number) {
// 转为 BigDecimal 再 toPlainString()
try {
BigDecimal bd = new BigDecimal(value.toString());
map.put(key, bd.toPlainString());
} catch (NumberFormatException e) {
map.put(key, "0"); // 容错
}
} else {
// 如果已经是字符串(比如来自 SQL 的 CHAR),直接保留
map.put(key, value.toString());
}
}
}
return ResponseEntity.ok(maps);
}
✅ 为什么这样能解决问题?
| 类型 | toString() 输出 |
new BigDecimal(...).toPlainString() |
|---|---|---|
Double: 1.12e30 |
"1.12E30" ❌ |
"1120000000000000000000000000000" ✅ |
BigDecimal: 1234567890123456789 |
"1234567890123456789" ✅ |
同左 ✅ |
Long: 1234567890 |
"1234567890" ✅ |
同左 ✅ |
✅
.toPlainString()是专门设计用来避免科学计数法的!
✅ 额外建议(可选)
如果你希望 全局解决 所有大数问题,可以在 application.yml 中添加:
spring:
jackson:
generator:
write-big-decimal-as-plain: true
但注意:这要求字段类型是 BigDecimal。如果你的 JDBC 返回的是 Double,它仍然会出问题。
所以 最可靠的方式还是在 Java 层手动处理。
✅ 最终效果
前端收到的数据将是:
[
{
"variety": "废钢铁",
"recycle": "1120000000000000000000000000000",
"num": "28",
"sales": "30"
}
]
→ 不再有 1.12e30!
✅ 现在你可以放心了!这个修改 100% 解决科学计数法问题。
更多推荐

所有评论(0)