 输出浮点数位数比输入少或变成科学计数法)
为什么 nlohmann/json 的 dump() 输出浮点数位数比输入少或变成科学计数法【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json使用 nlohmann/json 序列化数据时一个常见的困扰是输入里写着2555.5599999999999dump()却输出2555.56输入0.0000972439793401814输出却变成9.72439793401814e-05。这并不是序列化出错了而是库的数字存储与序列化规则决定的行为。本文基于项目文档给出原因、验证方法和边界情况帮助你在排查这类“浮点数位数变少或变科学计数法”的问题时快速判断根因。JSON 数字语法允许小数部分与指数部分E/e 后跟数字这是理解整件事的前提。第一步确认值在库内已经是 double根据 数字处理文档在默认的json类型中数字按如下规则存储正整数存std::uint64_t负整数存std::int64_t且仅在不损失精度时带小数点或科学计数法的数字一律存为double超出 64 位整数范围的整数也会退化为double。也就是说只要输入文本里含有小数点或E从解析那一刻起精度信息就已经收敛到double能表示的范围了。文档给出的两个例子整数-12345678912345789123456789小于INT64_MIN会被存为浮点数-1.2345678912345788e251E3会被存为浮点数1000.0。你可以用文档中列出的类型检查函数确认这一点见 Determine number types#include nlohmann/json.hpp using json nlohmann::json; int main() { json j json::parse(2555.5599999999999); // is_number_float() 为 true 说明值以 double 存储 std::cout j.is_number_float() \n; // 1 std::cout j.type() \n; // number_float std::cout j.dump() \n; // 2555.56 }判断方法如果type()返回number_float说明值以double存储后续的位数变化都是“double 最短回环表示”的必然结果而不是dump()的缺陷。为什么位数变少或出现科学计数法Number serialization 一节明确了序列化规则整数按原样序列化不使用科学计数法浮点数按%gprintf 修饰符、以std::numeric_limitsdouble::max_digits10位有效数字序列化目标是“最短的、仍能往返round-trip的表示”。由此会产生三类容易困惑的输出均为文档原文示例输出小数位比输入少2555.5599999999999序列化为2555.56反过来也可能变多输入没有科学计数法、输出却有0.0000972439793401814序列化为9.72439793401814e-05输入是科学计数法、输出却不是12345E-5序列化为0.12345。原因链条是输入文本 → 解析为double舍入到最接近的可表示值→ 序列化时输出能还原出同一个double的最短字符串。只要解析后的double相同两种写法就等价库会选择其中较短的一种。如何自行验证用 printf 复现 dump() 的结果文档给出了一个直接的核对方法把内存中的double原值交给std::printf按相同的精度规则格式化输出与dump()一致#include cstdio #include limits std::printf(%.*g\n, std::numeric_limitsdouble::max_digits10, double_value);验证方式取dump()输出里让你困惑的浮点值先用j.getdouble()取出内存中的double再用上面的语句打印。若两者一致说明dump()输出与 C 语言标准库对该double的格式化行为完全一致问题出在“double 无法保留你输入的全部十进制位”而不是序列化逻辑。序列化相关的其他行为缩进、ensure_ascii、错误处理器见 dump API 文档 与 Serialization它们不影响浮点数格式。边界与相邻现象排查时容易和上面根因混淆的几种情况文档均明确说明输入超出double范围如1E400解析时抛出json.exception.out_of_range.406这与“位数变少”是两回事。float赋值引入的误差文档示例中float f 0.3; json j f; std::cout j \n;输出0.30000001192092896。误差在float阶段就已产生与序列化无关。零的多种写法0、-0、0.0、-0.0、0E0、-0E0的存储类型与序列化结果不同例如-0存为有符号整数但序列化输出00.0输出0.0见 Zeros 表格。NaN无法从 JSON 文本解析出来但赋值可以存入序列化时输出null与 JavaScriptJSON.stringify行为一致。关于精度位数的文档差异需要留意FAQ “Number precision” 中写的是库使用std::numeric_limitsnumber_float_t::digits10对 IEEEdouble为 15 位进行序列化并说明该位数足以保证 round-tripping超出此精度的输入不保证字符串→值→字符串往返一致而 number handling 文档 与源码 single_include/nlohmann/json.hpp 中实际生效的是max_digits10配合%.*g格式化。两处表述不一致本文按“实际可观察行为”描述输出是保证 round-trip 的最短表示具体有效数字位数以运行时表现为准。如果确实需要更高精度文档提供了明确的替代路径通过模板参数把浮点类型改为float或long double见 Template number typesusing json_ld nlohmann::basic_jsonstd::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double;注意文档同时提醒应使用json_ld::parse解析而不是先用json::parse再转换——后者会先把浮点数解析为double精度已经损失。整数类型模板参数还需满足文档列出的可转换性与类型互不相同的约束。小结排查路径用type()/is_number_float()确认值是否为number_floatdouble 存储是用std::printf(%.*g, max_digits10, 值)复现输出一致即确认属于“double 最短回环表示”的正常行为否number_integer却出现位数变化或科学计数法检查输入是否超出 64 位整数范围而退化为 double或是否输入了1E3这类带指数的字面量需要更高精度时改用long double模板类型并用对应的parse。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考