authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2023-08-06 00:17:37-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2023-08-06 00:35:10-04:00
log1cce539ddcba1e21615e11dcf75f011074942414
tree2e7b979cbfcf9b7c3597f5094184a0bdcdf27214
parent7dacf7774523a454fce7216c13eb03c6badf8d7b

json.stringify: properly implement RFC8259 recommendation

The previous magic numbers used `1 << 52`, which did not account for the implicit leading one in the floating point format. The RFC is correct when it uses an exponent of 53. Technically these exclusive endpoints are also representable, but everyone including the RFC seems to use them exclusively. Also, delete special case optimizations related to the type which have already been implemented in the zig compiler to produce comptime values for tautological runtime comparisons.

1 files changed, 6 insertions(+), 8 deletions(-)

lib/std/json/stringify.zig+6-8
...@@ -34,7 +34,7 @@ pub const StringifyOptions = struct {...@@ -34,7 +34,7 @@ pub const StringifyOptions = struct {
34 /// Should unicode characters be escaped in strings?34 /// Should unicode characters be escaped in strings?
35 escape_unicode: bool = false,35 escape_unicode: bool = false,
3636
37 /// When true, renders numbers outside the range `±1<<53` (the precise integer range of f64) as JSON strings in base 10.37 /// When true, renders numbers outside the range `+-1<<53` (the precise integer range of f64) as JSON strings in base 10.
38 emit_big_numbers_quoted: bool = false,38 emit_big_numbers_quoted: bool = false,
39};39};
4040
...@@ -164,7 +164,7 @@ pub fn writeStreamArbitraryDepth(...@@ -164,7 +164,7 @@ pub fn writeStreamArbitraryDepth(
164/// * Zig `bool` -> JSON `true` or `false`.164/// * Zig `bool` -> JSON `true` or `false`.
165/// * Zig `?T` -> `null` or the rendering of `T`.165/// * Zig `?T` -> `null` or the rendering of `T`.
166/// * Zig `i32`, `u64`, etc. -> JSON number or string.166/// * Zig `i32`, `u64`, etc. -> JSON number or string.
167/// * If the value is outside the range `±1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number.167/// * If the value is outside the range `+-1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number.
168/// * Zig floats -> JSON number or string.168/// * Zig floats -> JSON number or string.
169/// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number.169/// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number.
170/// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00".170/// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00".
...@@ -402,13 +402,11 @@ pub fn WriteStream(...@@ -402,13 +402,11 @@ pub fn WriteStream(
402 pub fn write(self: *Self, value: anytype) Error!void {402 pub fn write(self: *Self, value: anytype) Error!void {
403 const T = @TypeOf(value);403 const T = @TypeOf(value);
404 switch (@typeInfo(T)) {404 switch (@typeInfo(T)) {
405 .Int => |info| {405 .Int => {
406 const emit_unquoted =
407 if (!self.options.emit_big_numbers_quoted) true
408 else if (info.bits < 53) true
409 else (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496));
410 try self.valueStart();406 try self.valueStart();
411 if (emit_unquoted) {407 if (!self.options.emit_big_numbers_quoted or
408 (value > -(1 << 53) and value < (1 << 53)))
409 {
412 try self.stream.print("{}", .{value});410 try self.stream.print("{}", .{value});
413 } else {411 } else {
414 try self.stream.print("\"{}\"", .{value});412 try self.stream.print("\"{}\"", .{value});