大促备战中的隐蔽陷阱:Double转String会使用科学计数法展示?

news/2026/1/26 17:12:49/文章来源:https://www.cnblogs.com/Jcloud/p/19534590

一、背景:大促备战中的异常数据

大促备战期间,接到客户反馈我司上传到客户服务器上的文件存在科学计数法表示的情况(下图的4.55058496E7),与约定不符。

 


 

查看转换前的数据是:455058496,转换后(除以10:进行毫米到厘米的转换)就变成了科学计数法形式了。

 


 

问题代码:

<set var="temp.b" expr="${_item.boxLength / 10}" clazz="java.lang.String"/>

说明:

这个是个EL表达式,含义是使用expr的值作为计算逻辑,计算结果赋值给var指向的变量temp.b,类型是java.lang.String

_item代表当前上下文里的一个对象。
boxLength_item对象所具备的属性。
该表达式先对boxLength执行除以 10 的运算,再把运算结果转换为字符串(由clazz定义的)。

业务上,boxLength是个长度的概念,单位是毫米,除以10是转换成厘米的含义。为了保证精度,系统(基于JAVA)会先将boxLength先转成java.lang.Double类型,再除以10,最后调用Double.toString()方法转成字符串。

二、问题定位:字符串转换的科学计数法陷阱

2.1 问题复现

代码:

Double depthInDouble = 455058496d/10;
log.info("depthInDouble={}", depthInDouble);

结果:

 


 

2.2 原因分析

问题就出在了最后一行,日志输出的时候Double会被转成String,调用Double.toString()方法,而对于Double对象的值在一定的范围内,会使用科学计数法表示。

log.info的调用链(为什么会调用到Double.toStirng()):

log.info("depthInDouble={}", depthInDouble);↓
Log4jLogger.info(String format, Object arg)↓
AbstractLogger.logIfEnabled(...)↓
AbstractLogger.logMessage(...)↓
ParameterizedMessageFactory.newMessage(...)↓
ParameterizedMessage 构造函数(参数被暂存为 Object[])↓
// 此时尚未调用 Double.toString()↓
// 当 Appender 执行输出时...
Appender.append(LogEvent)↓
LogEvent.getMessage().getFormattedMessage() // 触发消息格式化↓
ParameterizedMessage.getFormattedMessage()↓
ParameterizedMessage.formatMessage(...)↓
ParameterizedMessage.argToString(Object)↓
Double.toString() // 终于在这里被调用!

查看Double.toString()的源码,可以看到相关解释:

 


 

也就是说对于极小(小于10^-3)或者极大(大于10^7)值的浮点数,转成String的时候会使用科学计数法表示,验证如下。

代码:

public static void main(String args[]) {String depth = "455058496"; // 单位:毫米Double depthInDouble = Double.parseDouble(depth)/10;String doubleInString = String.valueOf(depthInDouble);log.info("depthInDouble={}", depthInDouble);log.info("doubleInString={}", doubleInString);depthInDouble = 1e-3;log.info("10^-3 = {}", depthInDouble);depthInDouble = 1e7;log.info("10^7 = {}", depthInDouble);Double aVerySmallNumber = 1e-9;depthInDouble = 1e-3 - aVerySmallNumber;log.info("10^-3 - delta = {}", depthInDouble);depthInDouble = 1e7 - aVerySmallNumber;log.info("10^7 - delta = {}", depthInDouble);}

运行结果:

 


 

说明,10^-3不会使用科学记计数法,但是小于它就会使用科学计数法,10^7就会使用科学计数法,小于它就会不会,大于它会。

2.3 为什么要使用科学计数法

2.3.1 小数在计算机内是如何表示的

先不急于讨论为什么使用科学计数法,我们先看看小数在计算机内是如何表示的。

从存储角度来看,计算机的存储是有限资源,能存储的数据是有范围的,不是无限大,也就是说有限的硬件资源限制了计算机可以表示的数值的大小。对于一个浮点数,我们可以用10个bit存储,也可以用100个,为了实现跨设备、跨平台的数据统一表示和交换,IEEE 754 规范定义了标准格式,规定了Double类型使用64比特。

 

 

当64个比特确定了,那么它可以表示的数字的范围就确定了,接下来考虑怎么表示小数,可以表示什么范围内的小数,进而再讨论威慑么定义超过10^7或者小于10^-3使用科学计数法,而不用普通的方式(定点数表示法)。

类似整数可以利用除以2取余获得其二级制的表示形式,例如:123(10进制)= 1111011(二进制)

 


 

小数则进行乘2取整,如0.123(10进制)= 0. 0001111101(二进制,位数会一直循环无法精确表示,只能近似,这里取了10位)

 


 

 

因此最简单的一种设计(不考虑正负)就是将64位中的一部分划分为整数位,一部分划分为小数位,比如32位整数,32位小数(定点数表示法)。

那么这样设计的Double最大数可以表示2^32-1,

如果要以米为单位表示银河系直径,约1光年299792458米/秒*1年 = 299792458米/秒*365天*86400秒/天 ≈ 9.45 * 10^15 ,而2^32-1≈4.29 * 10^9 (远小于1光年),因此无法使用Double表示银河系直径,无法支撑天文学科的计算了。

 


 

这样设计的Double最小可以表示2^-32=2.38*10^-10 ,一个质子的大小是0.84飞米=8.4*10^-16,因此也无法支持物理学的计算。

所以,矛盾在于增加整数部分的位数,就会压缩小数部分的位数,不同的领域中,既有要求数字很大可表示的(在乎量级,如天文学、金融学),也有要求数值很小能表示的(在乎精度,如物理学、生物学)。

可以看到,上面的很多数字表达,我们也使用了科学计数法的表示形式来简化表达,对于上面这个数字(9.454,254,955,488,000)写起来麻烦还很占地方,而且我们也不需要那么精确,只是看个量级,因此会写成9.45 * 10^15 ,不影响理解。

即表示一个极大或者极小的数可以使用:【数值*底数^指数】的形式,对于大数来讲指数就是正的,小数就是负的,计算机使用二进制,因此底数就是2,所以小数可以表示成:【数值*2^指数】的形式,这个数值,其实就是尾数。

计算机专家们经过多种研究,最终经过IEEE确定了IEEE 754标准,即不确定整数和小数的位数(固定小数点,即定点数),而使用变化的位数,也就是小数点可以浮动,即浮点数表示法。浮点数表示法定义了小数由符号位+指数位+尾数位三部分组成。

符号位是1bit,0代表整数,1代表负数,指数位决定数值的量级,尾数位决定数值精度。

64位的说明如下:

 


 

 

其中11和52的设计是在平衡了很多需求后得到的最佳实践。

Double (64位) = 符号位(1位) + 指数位(11位) + 尾数位(52位)示例:455058496.0 的IEEE 754表示
原始值:455058496.0
二进制科学计数法:1.0101100001110000000000000000000 × 2^28符号位:0 (正数)
指数位:28 + 1023(偏移量) = 1051 = 10000011011₂
尾数位:0101100001110000000000000000000... (52位)完整64位表示:
0 10000011011 0101100001110000000000000000000000000000000000000000

2.3.2 数值超过10^7或者小于10^-3会发生什么

其实什么也不会发生,只是基于如下原因综合权衡的结果。

1、认知科学依据

人类短期记忆的数字处理能力约为7±2位
超过7位的整数部分难以快速理解
科学计数法提供更好的可读性

2、精度保持考虑

10^7 = 10,000,000 (8位数字)
超过此值,普通格式会显得冗长
10^-3 = 0.001,更小的数用科学计数法更清晰

3、历史兼容性

这个标准在多种编程语言中被采用
保持了与C语言printf的兼容性
符合IEEE 754标准的建议

这也就是为什么这个这个范围内的数要表示成科学计数法了。

2.3.3 源码探究

1、调用链路

根据源码,可以看到Double.toString()方法的调用链是:

 


 

分流是否使用科学计数法的核心代码toChars的代码如下:

/** Formats the decimal f 10^e.*/
private int toChars(byte[] str, int index, long f, int e, FormattedFPDecimal fd) {/** For details not discussed here see section 10 of [1].** Determine len such that*     10^(len-1) <= f < 10^len*/int len = flog10pow2(Long.SIZE - numberOfLeadingZeros(f));if (f >= pow10(len)) {len += 1;}if (fd != null) {fd.set(f, e, len);return index;}/** Let fp and ep be the original f and e, respectively.* Transform f and e to ensure*     10^(H-1) <= f < 10^H*     fp 10^ep = f 10^(e-H) = 0.f 10^e*/f *= pow10(H - len);e += len;/** The toChars?() methods perform left-to-right digits extraction* using ints, provided that the arguments are limited to 8 digits.* Therefore, split the H = 17 digits of f into:*     h = the most significant digit of f*     m = the next 8 most significant digits of f*     l = the last 8, least significant digits of f** For n = 17, m = 8 the table in section 10 of [1] shows*     floor(f / 10^8) = floor(193_428_131_138_340_668 f / 2^84) =*     floor(floor(193_428_131_138_340_668 f / 2^64) / 2^20)* and for n = 9, m = 8*     floor(hm / 10^8) = floor(1_441_151_881 hm / 2^57)*/long hm = multiplyHigh(f, 193_428_131_138_340_668L) >>> 20;int l = (int) (f - 100_000_000L * hm);int h = (int) (hm * 1_441_151_881L >>> 57);int m = (int) (hm - 100_000_000 * h);if (0 < e && e <= 7) {return toChars1(str, index, h, m, l, e);}if(-3< e && e <=0){returntoChars2(str, index, h, m, l, e);}returntoChars3(str, index, h, m, l, e);}

代码地址: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/jdk/internal/math/DoubleToDecimal.java

可以看到使用科学计数法处理的核心代码是toChars3,代码如下:

private int toChars3(byte[] str, int index, int h, int m, int l, int e) {/* -3 >= e | e > 7: computerized scientific notation */index = putDigit(str, index, h);index = putChar(str, index, '.');index = put8Digits(str, index, m);index = lowDigits(str, index, l);return exponent(str, index, e - 1);
}

2、toChars3()的参数含义

byte[] str: 输出字符串的字节数组
int index: 当前写入位置的索引
int h: 最高位数字 (0-9)
int m: 中间8位数字 (00000000-99999999)
int l: 低位数字 (用于精度控制)
int e: 调整后的十进制指数值

3、 toChars3()的数据流处理步骤

1.putDigit(str, index, h) → 写入最高位数字
2.putChar(str, index, '.') → 写入小数点
3.put8Digits(str, index, m) → 写入中间8位数字
4.lowDigits(str, index, l) → 写入低位数字(去除尾随零)
5.exponent(str, index, e-1) → 写入指数部分

为什么使用 e-1?

原因:已经放置了一位数字在小数点前
目的:调整指数以保持数值不变
示例:4.55058496E7 表示 4.55058496 × 10^7

4、exponent()分析

标准科学计数法:a.bcd × 10^n
约束条件:1 ≤ a < 10(小数点前只有一位非零数字)
private int exponent(byte[] str, int index, int exp) {str[index++] = (byte) 'E';  // 写入字符 'E'if (exp < 0) {str[index++] = (byte) '-';  // 负指数写入 '-'exp = -exp;  // 转为正数处理}if (exp >= 100) {str[index++] = (byte) ('0' + exp / 100);  // 百位exp %= 100;}if (exp >= 10) {str[index++] = (byte) ('0' + exp / 10);   // 十位exp %= 10;}str[index++] = (byte) ('0' + exp);           // 个位return index;
}
输入参数: byte[] str(输出缓冲区)、int index(写入位置)、int exp(指数值)
核心功能: 将指数值格式化为字符串并写入字节数组
处理逻辑: 优化处理1位、2位、3位数的指数
1. 写入 'E'
2. 处理负号(如果 exp < 0)
3. 处理百位(如果 exp >= 100)
4. 处理十位(如果 exp >= 10)
5. 处理个位(必须)
返回值: 更新后的索引位置

例子:

1. 原始数值: 45505849.6
2. 精确指数: 7.658067227112319
3. 调整后指数: 7.658 - 1 = 6.658
4. 四舍五入: 7
5. exponent方法输入: exp = 7
6. 执行步骤:- 写入 'E' → index = 1- exp = 7 < 10,跳过百位和十位- 写入个位 '7' → index = 2
7. 输出: "E7"
8. 完整结果: "4.55058496E7"

根据源代码的逻辑简化了一版如下:

https://coding.jd.com/newJavaEngineerOrientation/Double2String.git

三、解决方案

3.1 BigDecimal 精准控制

new BigDecimal(doubleValue).setScale(2, RoundingMode.HALF_UP).toPlainString()

3.2 DecimalFormat 格式化

new DecimalFormat("#0.00").format(doubleValue) // 强制保留两位小数  

四、总结

Double 数值的字符串格式化规则(如 Double.toString())遵循:

普通格式(Plain):当数值的指数范围在 [-3, 7) 时(即绝对值在 [10^-3, 10^7) 之间),直接显示小数形式(如 0.001 或 123456.0)。
科学计数法(Scientific):当指数范围超出 [-3, 7)(如 0.000999 或 10000000.0),显示为科学计数法(如 9.99e-4 或 1.0e7)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1219991.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一文说明推荐优秀的系统特征交叉方法

一文说明推荐优秀的系统特征交叉方法2026-01-26 17:09 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !im…

2026年一体化污水处理设备公司权威推荐:絮凝沉淀池/mbr膜生物反应器/二氧化氯发生器/厌氧反应器/地埋式污水处理设备/选择指南

2026一体化污水处理设备实力厂商推荐据《2026-2030年中国污水处理设备行业发展白皮书》数据显示,2026年国内一体化污水处理设备市场规模同比增长18.7%,随着环保排放标准趋严,食品加工、畜禽养殖、市政生活等多领域对…

工程师之夜系列分享第三十九篇:Kafka、RocketMQ、JMQ 存储架构深度对比

引言 消息队列的存储架构是决定其可靠性、吞吐量、延迟性能的核心因素,直接影响业务场景适配能力。本文聚焦三款主流消息队列 ——Kafka(LinkedIn 开源,侧重高吞吐)、RocketMQ(阿里开源,金融级特性突出)、JMQ(…

红外碳硫分析仪怎么选不踩雷?对比生产厂家实力,共同锁定性价比好物

一、行业发展趋势:技术迭代与国产替代驱动市场爆发1.据第三方检测机构《2025年度国产高频红外碳硫分析仪品质评估报告》显示,2025年中国红外碳硫仪市场规模达3.8亿美元,年复合增长率超15%。这一增长由三大核心驱动力…

2025年度碳化硅定制服务排行榜,这五家口碑爆棚!精密铸造砂/白刚玉/棕刚玉/不锈钢灰/黑碳化硅/金刚砂/碳化硅/磨料碳化硅品牌找哪家

随着光伏新能源、第三代半导体、高端精密制造等战略性新兴产业的蓬勃发展,碳化硅作为关键的基础性材料,其市场需求正经历着从“标准化”向“定制化”的深刻转变。下游应用场景的多元化与精细化,对碳化硅的粒度分布、…

性能暴涨 1200 倍!我用 TypeScript 重构了这个经典项目

为什么需要重构&#xff1f; 起因&#xff1a;React Native 的"坑" 在将原版 relationship.js 集成到 React Native 移动端应用时&#xff0c;遇到了一个棘手的报错&#xff1a; ERROR [RangeError: Property storage exceeds 196607 properties] 这是因为原版 …

绝对干货! 网络安全面试题29问,(非常详细)零基础入门到精通,收藏这一篇就够了

1、php爆绝对路径方法&#xff1f; 单引号引起数据库报错 访问错误参数或错误路径 探针类文件如phpinfo 扫描开发未删除的测试文件 google hacking phpmyadmin报路径&#xff1a; /phpmyadmin/libraries/lect_lang.lib.php 利用漏洞读取配置文件找路径 恶意使用网站功…

Flink 2.0 从 flink-conf.yaml 到 config.yaml 的正确打开方式(含迁移与最佳实践)

1. Flink 配置加载机制&#xff1a;你改了文件但没生效&#xff1f;很可能就卡在这里 Flink 会在 Flink 进程启动时解析配置&#xff08;JobManager / TaskManager / HistoryServer 等进程启动时加载&#xff09;。所以&#xff1a; 改 config.yaml 之后必须重启相关进程才会生…

[特殊字符] 在浏览器地址栏输入 URL 后,页面是怎么一步步显示出来的?

这是一个前端面试 100% 会被问到的问题&#xff0c;但也是一个90% 的人答不完整的问题。你可能会说&#xff1a; “DNS 解析 ”“请求 HTML”“解析 DOM”“渲染页面” &#x1f449; 但如果继续追问&#xff1a; CSS 为什么会阻塞渲染&#xff1f;JS 为什么会卡住页面&…

动力电池点焊工艺解析

动力电池点焊机是电芯连接成组过程中的关键设备&#xff0c;其性能直接关系到电池包的结构完整性、电气安全性与长期可靠性。不同于普通的焊接设备&#xff0c;它针对动力电池的特定材料与工艺要求&#xff0c;在精度、一致性与可控性方面提出了严格标准。点焊工艺的核心&#…

【网络安全】红蓝对抗|备战大型攻防演练,这一篇就够了_红蓝对抗演练方案

大型攻防演练&#xff0c;红军镇守城池&#xff0c;与蓝军刺客般的灵活操作一较高下&#xff0c;双方在实战中倾尽 “巧思”&#xff0c;使攻击或防守达到事半功倍的效果。 “巧思” 为何&#xff0c;快上车&#xff0c;随我去战场一探究竟。 优秀的蓝军虽然要求团队每个人都…

2026年重庆职业高中综合实力排行榜 择校指南与各类需求全景解析

当前职业教育进入提质培优新阶段,重庆作为西部产业重镇,对技能人才的需求正朝着智能化、专业化、国际化方向升级。选择学校时不仅关注办学资质与规模,更看重实训配套、产教融合深度及定制化育人能力;家长与学生则聚…

永辉超市卡合规回收的平台怎么选,四大误区揭秘

整理旧物时,不少人会从抽屉深处翻出闲置的永辉超市卡。这些永辉超市卡因用不上而被遗忘,甚至临近有效期仍未启用。闲置的永辉超市卡并非只能沦为“沉睡物品”,通过合规的回收渠道就能合理盘活,但市场上的回收渠道良…

2026一体化污水处理设备优质公司推荐

2026一体化污水处理设备优质公司推荐行业背景与推荐依据据《2026-2030中国污水处理设备行业发展白皮书》数据显示,国内工业废水与生活污水年排放量超700亿吨,随着环保排放标准持续升级,全国范围内老旧污水处理设施改…

2026英语雅思零基础培训辅导机构推荐榜单 核心解析 助力家长精准匹配适配零基础辅导机构

对于计划让孩子备考雅思、规划留学,但孩子属于雅思零基础的家长而言,选择一家合适的英语雅思零基础培训辅导机构,是助力孩子迈出备考第一步、稳步夯实基础的核心困惑。很多家长翻遍各类机构介绍,却分不清哪家的零基…

孤能子视角:文心AI点评朝代分析系列

好的&#xff0c;我们来点评一下“孤能子”在CSDN上对中国主要朝代的分析。这位作者“孤能子”的视角非常独特且系统&#xff0c;他并非采用传统的历史学、社会学或政治学单一学科视角&#xff0c;而是构建了一个名为“EIS”&#xff08;能量-信息-结构&#xff09;的动力学模型…

2025年不容错过的烧菜火锅店,附近这6家实力上榜!社区火锅/特色美食/烧菜火锅/火锅/美食,烧菜火锅品牌排行

烧菜火锅市场:从“地方特色”到“消费刚需”的进化 近年来,烧菜火锅凭借“现烧现烫”的差异化模式,在火锅红海中开辟出细分赛道。其核心逻辑在于将川菜烧菜工艺与火锅场景深度融合,通过“一菜两吃”提升食材附加值…

孤能子视角:“心理学“

我将以EIS理论的“孤能子”视角&#xff0c;将心理学视为一个在人类认知场中演化的超级“学科孤能子”&#xff0c;并按照您之前设定的 “作者-作品-现实警示” 三步结构进行分析。请注意&#xff0c;此处的“作者”与“作品”需做适应学科特性的转义。 第一步&#xff1a;分析…

黑客技术之黑客常见10大攻击技术,你知道几个?_黑客一般采用哪些网络攻击技术

1、键盘监听 键盘监听就是利用一种监视间谍软件&#xff0c;将消息、电子邮件、击键信息等记录到一个日志文件中&#xff0c;或贩卖给他人。这其中就包含你的密码、社保号、信用卡信息等。 2、Ddos攻击 即利用众多来源的流量&#xff0c;涌入对方在线服务系统使其瘫痪。 黑…

孤能子视角:“精神分析“

(姑且当科幻小说看)第一步&#xff1a;分析“作者”——西格蒙德弗洛伊德&#xff08;作为精神分析“母体孤能子”&#xff09;启动&#xff1a;三力逼问&#xff0c;定位张力1. 零预设&#xff1a;不预设弗洛伊德是“科学先驱”或“江湖术士”&#xff0c;视其为在19世纪末维也…