首页
/ JSON for Modern C++ 数值类型检查:basic_json::is_number_float() 语义、用法与实现解析

JSON for Modern C++ 数值类型检查:basic_json::is_number_float() 语义、用法与实现解析

2026-09-07 14:31:13作者:翟江哲Frasier

is_number_float()nlohmann/json(JSON for Modern C++)basic_json 类提供的一组"类型检查"(type checker)成员函数之一,用于精确判断一个 JSON 值是否存储为浮点数(floating-point number)。本文以 官方 API 文档 is_number_float.md 为骨架,结合该仓库内 json.hppvalue_t.hpp 的源码实现与 unit-inspection.cpp 测试用例,完整讲解该函数的签名语义、返回值、异常与复杂度保证、全类型示例、底层判别原理,以及它与 is_number()is_number_integer()is_number_unsigned() 之间的分工关系。读完本文,你将掌握"如何在运行时无歧义地区分 JSON 中的整数与浮点数值",并能在解析用户输入或二次序列化前正确使用这些检查函数。

函数签名与核心语义

该函数的完整声明位于 include/nlohmann/json.hppbasic_json 类的公开接口区,官方文档给出的形式为:

constexpr bool is_number_float() const noexcept;

其语义是:当且仅当该 JSON 值是一个浮点数时返回 #!cpp true。这里的"浮点数"在类型层面是严格的——它排除了有符号整数与无符号整数,即便某个整数在数值上恰好等于某个可精确表示的浮点数,只要它以整数形式存储,is_number_float() 依然返回 false。反之,形如 23.421e3-0.5 这类带小数部分或指数部分的数值在解析后归属浮点存储类别,函数返回 true

与其他数值类型检查函数的分工

该函数是 nlohmann/json"数值三分类"检查体系的一员,与之并列的是:

  • is_number_integer():返回 #!cpp true 当且仅当值是(有符号或无符号)整数;
  • is_number_unsigned():返回 #!cpp true 当且仅当值是无符号整数;
  • is_number():返回 #!cpp true 当且仅当值是任一类型的数字,即整数或浮点数的并集。

它们各自回答一个不同的问题,组合起来可构成完整的判别矩阵:

JSON 值示例(存储方式) is_number() is_number_integer() is_number_unsigned() is_number_float()
有符号整数(如 17 true true false false
无符号整数(如 12345678987654321u true true true false
浮点数(如 23.42 true false false true
字符串 / 数组 / 对象 / null / 布尔 / 二进制 false false false false

is_number.md 文档 中甚至直接给出了 is_number() 的"可行实现"——return is_number_integer() || is_number_float();,这恰好印证了三者之间的逻辑关系。

返回值、异常安全与复杂度保证

官方文档对本函数的三个契约性保证如下,需逐一理解其工程含义:

  • 返回值:类型为浮点数时返回 #!cpp true,否则返回 #!cpp false
  • 异常安全No-throw guarantee(不抛出任何异常),函数声明中携带的 noexcept 关键字即编译期契约;
  • 复杂度常数级(Constant)

这三点之所以成立,原因在于该函数的实现只是"比较一个类型标签",不涉及内存分配、迭代或用户代码调用。这在下面"底层实现"一节将得到源码级验证。得益于 constexprnoexcept 的双重修饰,该函数不仅可在运行时安全检查,理论上也具备在常量表达式(如 static_assert)中使用的前提(在满足 C++ 标准的 constexpr 求值条件下)。

覆盖全部 JSON 类型的完整示例

官方文档对应的可编译示例位于 examples/is_number_float.cpp,它一次性覆盖了 nlohmann/json 支持的全部九类值:null、布尔、整数、无符号整数、浮点数、对象、数组、字符串与二进制数据。示例源码如下:

#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create JSON values
    json j_null;
    json j_boolean = true;
    json j_number_integer = 17;
    json j_number_unsigned_integer = 12345678987654321u;
    json j_number_float = 23.42;
    json j_object = {{"one", 1}, {"two", 2}};
    json j_array = {1, 2, 4, 8, 16};
    json j_string = "Hello, world";
    json j_binary = json::binary({1, 2, 3});

    // call is_number_float()
    std::cout << std::boolalpha;
    std::cout << j_null.is_number_float() << '\n';
    std::cout << j_boolean.is_number_float() << '\n';
    std::cout << j_number_integer.is_number_float() << '\n';
    std::cout << j_number_unsigned_integer.is_number_float() << '\n';
    std::cout << j_number_float.is_number_float() << '\n';
    std::cout << j_object.is_number_float() << '\n';
    std::cout << j_array.is_number_float() << '\n';
    std::cout << j_string.is_number_float() << '\n';
    std::cout << j_binary.is_number_float() << '\n';
}

程序输出(见 is_number_float.output):

false
false
false
false
true
false
false
false
false

注意第 4 行:12345678987654321u 虽然数值巨大,但因其带 u 后缀而被构造为无符号整数存储,因此 is_number_float() 依旧返回 false——再一次印证了该函数判别的是存储类型而非数值大小

底层实现:类型标签比较驱动的 constexpr 判定

is_number_float() 的实现极简,直接位于 include/nlohmann/json.hpp#L1414-L1419

constexpr bool is_number_float() const noexcept
{
    return m_data.m_type == value_t::number_float;
}

其本质是一次 value_t 枚举标签的相等比较。value_t 是库内部用于区分不同 JSON 类型(null、object、array、string、boolean、number_integer、number_unsigned、number_float、binary、discarded)的核心枚举,定义于 include/nlohmann/detail/value_t.hpp#L53-L65。与之对应,同一处源码还展示了姊妹函数的实现方式:

constexpr bool is_number() const noexcept          // m_data.m_type == ... number_integer/unsigned/float 的并
{
    return is_number_integer() || is_number_float();
}

constexpr bool is_number_integer() const noexcept  // number_integer || number_unsigned
{
    return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
}

constexpr bool is_number_unsigned() const noexcept // 仅 number_unsigned
{
    return m_data.m_type == value_t::number_unsigned;
}

由此可清晰理解:nlohmann/json 并非简单地将 JSON 数字一律映射为 double,而是为了让 C++ 侧能够"更精确地存储",把数值细分为三个枚举成员(有符号整数、无符号整数、浮点数)。这种设计正是 is_number_integer()/is_number_unsigned()/is_number_float() 三函数并存的根本原因,其设计动机在 value_t.hpp 的注释中有明确说明。

解析阶段浮点类别是如何被"打标"的

浮点类型标签并非凭空产生,而是在解析数字字面量时由词法分析器(lexer)决定。nlohmann/json 的词法扫描函数 scan_number()(见 include/nlohmann/detail/input/lexer.hpp,起始于约第 1002 行)实现了一个基于 goto 的状态机。状态转移表(约第 977–981 行)表明:当扫描到小数点或 e/E 指数符号时,数字会转入 decimal/exponent 相关状态,从而不可能再归类为纯整数 token。

在扫描完成的收尾阶段(lexer.hpp#L1277-L1327),代码先尝试用 std::strtoull/std::strtoll 把 token 解析为无符号/有符号整数,仅当解析溢出(errno == ERANGE)或 token 本身带小数点/指数时,才回退到 strtof(库内高性能浮点解析)并返回 token_type::value_float

// this code is reached if we parse a floating-point number or if an
// integer conversion above failed
strtof(value_float, token_buffer.data(), &endptr);
...
return token_type::value_float;

从源码结构看,这意味着两个推论:其一,形如 1.02e3 的 JSON 文本即使在数学上是整值,只要字面上含小数点或指数就会被标为浮点;其二,超出 std::int64_t/std::uint64_t 表示范围的大整数也会降级为浮点存储,从而被 is_number_float() 判为 true。这些行为构成了"同一份 JSON 数据在不同写法下可能落入不同类型标签"的边界情形,也正是类型检查函数需要存在的原因。

浮点底层存储类型 number_float_t 与边界行为

浮点数值在库中经由模板参数 NumberFloatType 抽象出的别名 number_float_t 存储(详见 number_float_t.md 文档json.hpp#L393-L394using number_float_t = NumberFloatType;)。默认情况下:

  • NumberFloatType 默认为 double,因此 number_float_t 默认为 IEEE-754 双精度类型;
  • 超出 double 表示范围(小于 -1.79769313486232e+308 或大于 1.79769313486232e+308)的数值在内部被存储为 NaN,并在序列化时输出为 null
  • NaN(非数值)同样会被序列化为 null;序列化逻辑中对 NaN 的规避可在输出模块 serializer.hppdump_float 系列函数中看到依据。

理解这一点对正确使用 is_number_float() 很关键:该函数只能回答"当前是否按浮点存储",不能保证该浮点值一定来自合法的有限 JSON 数字——极端越界数值会以 NaN 形式存在,此时配合数值检查与范围校验才安全。

测试佐证:完整的类型判别矩阵

类型检查语义在测试套件中被逐类型验证。核心测试文件 tests/src/unit-inspection.cpp 中针对 object、array、null、boolean、integer、unsigned integer、floating-point、string、binary、discarded 等每种值分别断言了全部类型检查函数的结果。其中浮点分支(约第 148–164 行):

SECTION("number (floating-point)")
{
    json const j(42.23);
    ...
    CHECK(j.is_number());
    CHECK(!j.is_number_integer());
    CHECK(!j.is_number_unsigned());
    CHECK(j.is_number_float());
    ...
}

对应地,对象/数组/null/布尔等分支中都以 CHECK(!j.is_number_float()) 断言其反例(如第 30、48、66 行等)。此外,unit-regression1.cpp(约第 153–171、677–683、897–903、1096、1191 行)在回归场景中反复使用 is_number_float() 校验浮点解析结果的正确归属,unit-ubjson.cpp(第 804 行)与 unit-bjdata.cpp(第 1332 行)则在二进制格式(UBJSON/BJData)往返解析后再次断言值仍保持浮点类别。这说明该函数是序列化、反序列化全链路中稳定可靠的"类型探针"。

典型实战场景

综合前述语义与实现,is_number_float() 最常见的落地场景包括:

  1. 输入校验与分流:解析外部 JSON 后,先用 is_number_float() / is_number_integer() 区分数值形态,再决定按整数还是浮点处理,避免把 23.42 截断成整数导致数据损坏;
  2. 格式化策略选择:需要自定义序列化时,先判断 is_number_float() 再选择"保留小数位/指数形式"的输出路径,与 serializer.hpp 内部对浮点与整数分别走 dump_float 与整数路径的做法一致;
  3. 数据迁移与类型审计:遍历 JSON 树时用 is_number_float() 找出所有浮点字段,配合 number_float_t 的精度边界评估(如金额、ID 等应使用整数存储的字段)是否会在二次处理中发生精度漂移;
  4. get<T>()/指针访问协同:在调用 get<number_float_t>()get_ptr<number_float_t>() 等取值操作前先做类型守卫,规避类型不匹配导致的行为差异。

版本沿革

根据官方文档的 version history:is_number_float()1.0.0 版本起即已提供。与之相对,is_number() 在 2.0.0 版本扩展为同时对无符号整数返回 true。这一沿革提醒使用者:在涉及跨版本兼容的历史代码中,"数字"判定的口径曾发生过演进,而精确到 is_number_float() 这一层的判定语义则始终稳定,适合作为不依赖版本差异的可靠判断依据。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391