在 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) 返回的是 DoubleBigDecimal,而 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% 解决科学计数法问题

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐