encode_number_precision:控制浮点编码输出精度
概述
为 qjson 添加 encode_number_precision(precision) API,和 lua-cjson / OpenResty lua-cjson 的行为一致。允许调用方控制 qjson.encode() 中浮点数的有效位数,用于金融、科学计算等需要精确数字格式的场景。
需求
场景
当 JSON 包含浮点数时,调用方需要通过配置有效位数来控制输出格式:
local qjson = require("qjson")
qjson.encode_number_precision(3)
print(qjson.encode({value = 123.456789})) -- '{"value":123}'
qjson.encode_number_precision(8)
print(qjson.encode({value = 123.456789})) -- '{"value":123.45679}'
qjson.encode_number_precision(14) -- 恢复默认
行为规范
| 调用 |
行为 |
qjson.encode_number_precision() |
返回当前精度值(默认 14) |
qjson.encode_number_precision(N) |
设置精度为 N(1-14),返回旧值 |
qjson.encode_number_precision(-1) |
抛出错误 |
qjson.encode_number_precision(3.5) |
抛出错误(非整数) |
qjson.encode_number_precision(15) |
抛出错误(超出上限 14) |
错误信息格式:"expected integer between 1 and 14"(与 lua-cjson 的 CJSON_MAX_NUM_PRECISION 保持一致)
对 encode 的影响
- 精度仅影响
encode_number() 中的浮点分支(string_format("%.Ng", n))
- 整数分支(
math.floor(n) 且 abs(n) < 1e15)不受影响,继续使用 %d
- lazy proxy 的
__tostring 受精度影响(因为走 encode 路径)
- lazy proxy 的快速路径(未变动的子树,直接切片原字节)不受影响
设计
方案:模块级全局变量 + getter/setter
在 lua/qjson/table.lua 中添加模块级状态和 getter/setter 函数,修改 encode_number 使用该变量。
状态:
-- 模块私有;setter 同步更新二者,encode_number 直接复用缓存的格式串。
local _ENCODE_NUMBER_PRECISION = 14
local _ENCODE_NUMBER_FMT = "%.14g"
getter/setter:
function _M.encode_number_precision(precision)
if precision == nil then
return _ENCODE_NUMBER_PRECISION
end
if type(precision) ~= "number"
or precision < 1
or precision > 14
or precision ~= math.floor(precision) then
error("expected integer between 1 and 14")
end
local old = _ENCODE_NUMBER_PRECISION
_ENCODE_NUMBER_PRECISION = precision
_ENCODE_NUMBER_FMT = "%." .. precision .. "g"
return old
end
encode_number 修改:
将硬编码的 "%.14g" 改为复用缓存的格式串 _ENCODE_NUMBER_FMT:
local function encode_number(n)
if n ~= n or n == math.huge or n == -math.huge then
error("qjson.encode: cannot encode non-finite number")
end
if n == math.floor(n) and math.abs(n) < 1e15 then
return string_format("%d", n)
end
return string_format(_ENCODE_NUMBER_FMT, n)
end
导出(lua/qjson.lua):
_M.encode_number_precision = _lazy.encode_number_precision
精度生效路径一览
qjson.encode(v)
└─ encode(v, depth, active)
└─ case "number": encode_number(n)
├─ 整数分支 → string_format("%d", n) (精度不适用)
└─ 浮点分支 → string_format(_ENCODE_NUMBER_FMT, n) (缓存的格式串,随精度更新)
tostring(lazy_proxy)
└─ encode_proxy(t, depth, active)
├─ 快速路径(!_dirty)→ 返回原 JSON 字节切片 (不经过 encode_number)
└─ 慢速路径 → encode_lazy_*_walking → encode() (经过 encode_number,精度生效)
边界情况
| 场景 |
行为 |
| 精度 1 |
123.456 → "1e+02" |
| 精度 5 |
3.14159265 → "3.1416"(5 位有效数字) |
| 精度 14(默认 / 上限) |
0.1 → "0.1";%.14g 不会暴露双精度尾数噪声 |
| NaN/Infinity |
精度不影响;encode_number 直接报错 |
| cdata(int64_t/uint64_t) |
走 encode_cdata 路径,精度不适用 |
并发 / 生命周期语义
_ENCODE_NUMBER_PRECISION 是模块级状态,require("qjson") 后在整个 Lua VM 内共享,不随单次 encode() 调用复位。
- 在 OpenResty 下该状态是 per-worker 的:同一 worker 内某个请求调用 setter 会影响该 worker 后续所有请求。这与 lua-cjson 全局配置的语义一致。
- 需要请求级隔离的调用方应在用完后显式恢复:
local old = qjson.encode_number_precision(n); ...; qjson.encode_number_precision(old)。
性能
- 格式串在 setter 里预先拼好并缓存为
_ENCODE_NUMBER_FMT,encode_number 直接复用,浮点编码热路径上无字符串拼接开销
- 整数分支沿用常量
"%d",不受精度设置影响
测试
describe("encode_number_precision", function()
it("defaults to 14", function()
assert.are.equal(14, qjson.encode_number_precision())
end)
it("getter returns current value", function()
qjson.encode_number_precision(8)
assert.are.equal(8, qjson.encode_number_precision())
qjson.encode_number_precision(14)
end)
it("setter returns previous value", function()
local old = qjson.encode_number_precision(3)
assert.are.equal(14, old)
qjson.encode_number_precision(14)
end)
it("affects float encoding", function()
qjson.encode_number_precision(3)
local out = qjson.encode({a = 1.23456})
assert.are.equal('{"a":1.23}', out)
qjson.encode_number_precision(14)
end)
it("does not affect integer encoding", function()
qjson.encode_number_precision(3)
local out = qjson.encode({a = 12345})
assert.are.equal('{"a":12345}', out)
qjson.encode_number_precision(14)
end)
it("rejects non-integer precision", function()
assert.has_error(
function() qjson.encode_number_precision(3.5) end,
"expected integer between 1 and 14"
)
end)
it("rejects precision below 1", function()
assert.has_error(
function() qjson.encode_number_precision(0) end,
"expected integer between 1 and 14"
)
end)
it("rejects precision above 14", function()
assert.has_error(
function() qjson.encode_number_precision(15) end,
"expected integer between 1 and 14"
)
end)
it("accepts boundary values 1 and 14", function()
assert.are.equal(14, qjson.encode_number_precision(1))
assert.are.equal(1, qjson.encode_number_precision(14))
end)
it("affects lazy proxy __tostring", function()
local t = qjson.decode('{"a":1.23456}')
qjson.encode_number_precision(3)
t.a = nil
t.a = 1.23456
local out = qjson.encode(t)
assert.are.equal('{"a":1.23}', out)
qjson.encode_number_precision(14)
end)
end)
需修改的文件
lua/qjson/table.lua — 核心实现(状态变量 + getter/setter + encode_number 修改)
lua/qjson.lua — 对外导出
docs/migrating-from-cjson.md — 将 encode_number_precision 从 Unsupported 表中移除
tests/lua/ — 新增 encode_number_precision_spec.lua
不在范围内
- Rust/FFI 侧变更(纯 Lua 层功能)
- 每次
encode() 调用传入 precision 参数(仅模块全局设置)
- 整数格式精度控制(整数使用
%d,独立于该设置)
- lazy proxy 快速路径的精度控制(原字节不变,无重新编码)
encode_number_precision:控制浮点编码输出精度
概述
为 qjson 添加
encode_number_precision(precision)API,和 lua-cjson / OpenResty lua-cjson 的行为一致。允许调用方控制qjson.encode()中浮点数的有效位数,用于金融、科学计算等需要精确数字格式的场景。需求
场景
当 JSON 包含浮点数时,调用方需要通过配置有效位数来控制输出格式:
行为规范
qjson.encode_number_precision()qjson.encode_number_precision(N)qjson.encode_number_precision(-1)qjson.encode_number_precision(3.5)qjson.encode_number_precision(15)错误信息格式:
"expected integer between 1 and 14"(与 lua-cjson 的CJSON_MAX_NUM_PRECISION保持一致)对
encode的影响encode_number()中的浮点分支(string_format("%.Ng", n))math.floor(n) 且 abs(n) < 1e15)不受影响,继续使用%d__tostring受精度影响(因为走encode路径)设计
方案:模块级全局变量 + getter/setter
在
lua/qjson/table.lua中添加模块级状态和 getter/setter 函数,修改encode_number使用该变量。状态:
getter/setter:
encode_number 修改:
将硬编码的
"%.14g"改为复用缓存的格式串_ENCODE_NUMBER_FMT:导出(lua/qjson.lua):
精度生效路径一览
边界情况
123.456→"1e+02"3.14159265→"3.1416"(5 位有效数字)0.1→"0.1";%.14g不会暴露双精度尾数噪声encode_number直接报错encode_cdata路径,精度不适用并发 / 生命周期语义
_ENCODE_NUMBER_PRECISION是模块级状态,require("qjson")后在整个 Lua VM 内共享,不随单次encode()调用复位。local old = qjson.encode_number_precision(n); ...; qjson.encode_number_precision(old)。性能
_ENCODE_NUMBER_FMT,encode_number直接复用,浮点编码热路径上无字符串拼接开销"%d",不受精度设置影响测试
需修改的文件
lua/qjson/table.lua— 核心实现(状态变量 + getter/setter + encode_number 修改)lua/qjson.lua— 对外导出docs/migrating-from-cjson.md— 将 encode_number_precision 从 Unsupported 表中移除tests/lua/— 新增encode_number_precision_spec.lua不在范围内
encode()调用传入 precision 参数(仅模块全局设置)%d,独立于该设置)