人们通常不会思考这个问题,似乎相当直接。它是一个数字,显而易见!但事实证明,这个问题有点难以回答,对于 API 设计者来说尤为重要!所以让我们通过深入研究各种 JSON 规范和实现来探索一下。研究结果将在最后进行总结,如果你只想知道答案而不想深入探讨,可以直接跳到底部。
权威来源
JSON 由两个主要标准定义:Ecma-404 和 RFC 8259。这两个标准在语义上是相同的,但 RFC 8259 对于良好的互操作性提供了一些额外的建议。一个相关的标准,RFC 7493,描述了与互联网 JSON 格式密切相关的内容,这是 JSON 的一个受限配置文件,它在 RFC 8259 中的建议上添加了更多限制。此外,在 API 描述的上下文中,JSON Schema 定义了一个数字数据类型,也被 OpenAPI 正式引用。让我们分别查看这些规范以获取线索。
Ecma-404
数字是一个由十进制数字序列组成的内容,没有多余的前导零。它可能有一个前置的减号(U+002D)。它可能有一个由小数点(U+002E)前缀的小数部分。它可能有一个指数,由 e(U+0065)或 E(U+0045)前缀,以及可选的+(U+002B)或-(U+002D)组成。
所以,一个 JSON 数字是一个由数字组成的序列,可选有符号、小数部分和指数。这个描述纯粹是句法上的。
RFC 8259
该规范提供了与 Ecma-404 提供的铁路图等价的 ABNF 语法。它还明确允许实现对接受的数字的范围和精度设置限制[1]。它继续指出:
由于实现 IEEE 754 二进制 64 位(双精度)数字的软件通常是可用的并且被广泛使用,通过期望的精度和范围不超过这些数字的实现,可以实现良好的互操作性
这指出一些 JSON 实现使用双精度来存储 JSON 数字值。在世界各地使用的浏览器中找到的实现就是其中之一。因此,如果 JSON 数字的范围和精度适合于双精度,那么它将是可互操作的。
RFC 7493
该规范使 RFC 8259 中的信息性注释变成了一个规范的 SHOULD NOT:
I-JSON 消息不应包含比 IEEE 754 双精度数字提供的更大数量或精度的数字
它继续建议,如果需要更大的范围或精度,那么应该将数字编码为字符串。
JSON Schema 和 OpenAPI
JSON Schema 将数字描述为:
JSON "number" 值的任意精度的十进制数值
JSON Schema 和 OpenAPI 还定义了整数的概念。JSON Schema 根据其值定义整数,即零小数部分的数字。它还指出整数值不应该被编码为带小数部分的数字。OpenAPI 根据其语法定义整数,即没有小数部分或指数部分的 JSON 数字。
实际中的 JSON 数字
RFC 8259 引出了一个重要的观点,即最终实现决定了一个 JSON 数字是什么。在实践中,范围和精度是有限的,但是它们是什么呢?我们知道至少有一个被广泛部署的实现被限制为双精度。还有其他需要考虑的互操作性问题吗?让我们通过两个平行的轨道来进行调查:跨一些常见语言的 JSON 解析器和序列化器,以及在 OpenAPI 生态系统中的代码生成器。
语言实现
语言实现最终决定了 JSON 数字是什么,因此让我们看看一些示例并检查常见模式。对于具有可配置序列化/反序列化的语言,只涵盖默认行为。
JavaScript
JavaScript 的内置 JSON 实现仅使用内置的 Number
类型,因此所有值都受到双精度范围和精度的限制。默认情况下不支持 BigInt
的序列化。JavaScript 还使得保留数值文字在进行往返时变得不可能,例如像 1.0
这样的整数作为小数会被重新放回线上成为 1
。有一个语言提案,可以解决这两个问题,而不需要替换整个解析器。
Python 3.8
整数、小数和指数被不同对待。整数可以在 -104299 到 104299[2] 范围内往
返为 JSON 数字,而小数和指数使用双精度,因此受到双精度范围和精度的限制。超出 int
范围的整数序列化会导致 ValueError
。超出双精度范围的小数和指数会导致 inf
。在解析然后序列化指数时,指数格式将丢失。
C#(.NET 8,System.Text.JSON)
C# 的 System.Text.JSON
库是处理 JSON 数据的推荐方式,尽管 Newtonsoft.JSON
也常用。我们来检查前者的行为,后者可能有所不同。
C# 支持通过使用 TryGet*
API 将数据反序列化为适当的数据类型。使用此 API,可以无损地将整数类型反序列化为 int64
,将稍大的整数反序列化为 decimal
。decimal
也可以用来表示小数值,可能会有精度损失。如果您事先知道模式,可以添加对其他数据类型(如 BigInteger
)的反序列化支持。
C# 还支持获取文字的原始文本,这样可以自定义处理和往返任意文字,而不会丢失精度。
Java(JDK 11+,Jackson)
Java 通常使用 Jackson
库来处理 JSON 序列化和反序列化。Jackson
允许将数据序列化和反序列化为任何 Java 数值类型,包括 BigDecimal
,使其能够表示任何范围和精度的数字文字,而不会丢失精度。
Rust(serde)
Rust 的 serde_json
库通常用于 JSON 序列化和反序列化。它支持反序列化适合于 i64
/u64
范围内的整数值。它还支持反序列化适合于 f64
范围内的整数和小数值,尽管这样做可能会导致精度损失。超出 f64
范围的整数和小数会导致错误。指数始终反序列化为双精度。然而,serde
有一个 arbitrary_precision
配置标志,可以在不丢失精度的情况下往返任意数值,假设它们没有以损失的方式反序列化。可以通过一些额外的代码添加对其他数据类型的反序列化支持,但需要知道数据的模式。
Go
Go 的 encoding/json
库能够动态地反序列化 JSON 数字文字使用 float64
类型。如果事先知道模式,可以将已知整数解析为适当的整数类型,最多到 int64
,并根据需要将小数解析为 float32
类型。可以通过一些额外的代码添加对反序列化例如小数类型或大整数的支持,但也需要知道数据的模式。
可以指示 Go 将数字文字反序列化为字符串,从而允许对任意文字进行自定义处理和往返,而不会丢失精度。
摘要
为了总结各种实现的行为,我们可以查看它们对以下值的行为:
literal-table tr td:nth-child(1), #literal-table tr th:nth-child(1) { text-align: right; }
数字文字 | 描述 |
---|---|
10 | 小整数 |
1000000000 | 中等整数:超出 int32 范围,但在 int64 范围内 |
10000000000000001 | 大整数:超出双精度范围,但在 int64 范围内 |
100000000000000000001 | 极大整数:超出 int64 范围 |
1[309 个零] | 超出 decimal128 范围的巨大整数 |
10.0 | 低精度小数 |
10000000000000001.1 | 高精度小数:精度 > 双精度 |
1.[34 个 1] | 极高精度小数:精度 > decimal128 |
1E2 | 小指数 |
1E309 | 大指数:超出 float 范围 |
下表显示了每种语言中用于表示字面值的数据类型。灰色单元格表示错误。红色单元格表示存在精度损失的非错误情况。这里只涵盖默认序列化行为。通过配置序列化程序或通过其他机制,可能可以防御这些类型的错误。此外,我使用的测试代码尝试使用动态/无模式的解析路径。对于某些语言,事先了解模式可以获得更好的行为。测试代码可以在附录中找到。
OpenAPI 代码生成器
在 JSON API 的背景下,可以提出这样的论点:OpenAPI 及其代码生成器的生态系统与各种实现中找到的解析器一样重要。即使语言的 JSON 解析器能够解析特定大小的数字字面量,使用 OpenAPI 的签名可能更或更少地受限制,特别是对于强类型语言。
为了了解各种语言的行为,我们将使用 OpenAPI 3 规范本身定义的数字类型和格式,以及OpenAPI 格式注册表中定义的数字格式。
类型 | 格式 | 描述 |
---|---|---|
number | 任意精度,十进制基数的十进制数字值 | |
integer | 没有小数部分或指数部分的 JSON 数字 | |
number | float | 单精度浮点数 |
number | double | 双精度浮点数 |
number | decimal | 未指定精度和范围的定点十进制数 |
number | decimal128 | 具有 34 个有效十进制数字的十进制浮点数 |
integer | int8 | 有符号 8 位整数 |
integer | uint8 | 无符号 8 位整数 |
integer | int16 | 有符号 16 位整数 |
integer | int32 | 有符号 32 位整数 |
integer | int64 | 有符号 64 位整数 |
除了 uint8
之外,OpenAPI 或格式注册表中都未定义无符号整数。最近添加了 double-int
[3],但不太可能在任何地方得到支持。
下表总结了每种语言的输出,使用了OpenAPI-Generator代码生成器。红色突出显示的单元格显示了生成的代码创建了一种情况,其中在 OpenAPI 规范定义的范围和精度中提供某些值时可能会出现精度损失或错误。
OpenAPI | JavaScript | C# | Python | Java | Go | Rust |
---|---|---|---|---|---|---|
number | number | decimal128 | int, float | BigDecimal | float32 | f32 |
integer | number | int32 | int | Integer | int32 | i32 |
int8 | number | int32 | int | Integer | int32 | i32 |
uint8 | number | int32 | int | Integer | int32 | i32 |
int16 | number | int32 | int | Integer | int32 | i32 |
int32 | number | int32 | int | Integer | int32 | i32 |
double-int | number | int32 | int | Integer | int32 | i32 |
int64 | number | int64 | int | Long | int64 | i64 |
single | number | float | int, float | Float | float32 | f32 |
double | number | double | int, float | Double | float64 | f64 |
decimal | number | decimal128 | int, float | BigDecimal | float32 | f32 |
decimal128 | number | decimal128 | int, float | BigDecimal | float32 | f32 |
从中我们可以看出,OpenAPI-Generator 套件将 integer
视为 int32,尽管规范暗示它具有任意范围。因此,当使用这些工具时,似乎没有办法在所有具有相应数据类型的语言中定义任意长度的整数。此外,尽管规范暗示 number
具有任意范围和精度,但它通常被理解为 32 位的 float
。
结论摘要
那么问题来了:什么是 JSON 数字?
- 根据权威规范,是任意长度和精度的数字字面量。
- 根据 JSON 的可互操作文件格式,是具有双精度长度和精度的数字字面量。
- 根据各种 JSON 实现,是具有根据实现不同而不同的约束的数字字面量。
- 根据 OpenAPI,是:
- 任意长度的整数
- 任意长度和精度的十进制值
- 根据 OpenAPI 代码生成器,
- 没有格式时,是:
- 对于
number
,一些小到 float32 的浮点表示 - 对于
integer
,是 int32
- 对于
- 有格式时,是:
- 在该语言中最接近指定格式的数据类型
- 如果不支持该格式,则是
int32
或其最接近的近似形式。
- 没有格式时,是:
我们还确认了双精度范围之外的数字的可互操作性不稳定。所有测试的实现都可以安全地交换双精度范围内的数字。除了 JavaScript 外,所有实现都可以在 int64
范围内传递整数字面量(尽管 Go 需要事先了解模式)。
对于那些使用 OpenAPI 的人来说,我们可以推断出一些定义使用数字的 API 的最佳实践:
- 在 OpenAPI 中始终指定格式。
integer
或number
都不太可能得到您想要的结果。 - 避免使用
double-int
。它不受支持,并且在许多语言中会导致潜在的错误或数据丢失。 - 避免使用
decimal
和decimal128
。它们也不被广泛支持。 - 如果您有 JavaScript 消费者,请避免使用带有
number
类型的int64
。改为使用string
类型。
如果您使用 TypeSpec,则可以总结为以下建议:
- 避免使用
decimal
、decimal128
、integer
和numeric
类
型。
- 如果您使用
int64
并且您有 JavaScript 消费者,请通过@encode("int64", string)
将其编码为字符串。
最后,由于某些语言以不同的方式处理具有指数和小数部分的数字字面量,实现应在往返时保留格式(例如,10.0
应该被放回为 10.0
)。
附录:测试代码
此代码很糟糕,LLMs 在其创建中发挥了重要作用。输出需要一些解释。请随意在 GitHub 上提出改进建议。
JavaScript
const jsonValues = [
"10",
"1000000000",
"10000000000000001",
"100000000000000000001",
"1" + "0".repeat(309),
"10.0",
"10000000000000001.1",
"1.1111111111111111111111111111111111",
"1E2",
"1E309",
];
for (const jsonValue of jsonValues) {
console.log(`Testing JSON value: ${jsonValue}`);
try {
// Deserialize the JSON value
const deserialized = JSON.parse(jsonValue);
if (String(deserialized) !== jsonValue) {
console.log("precision loss detected", jsonValue, deserialized);
}
const serialized = JSON.stringify(deserialized);
if (jsonValue !== serialized) {
console.log("round-trip error detected", jsonValue, serialized);
}
} catch (error) {
console.log(`Deserialization error: ${error.message}`);
}
console.log();
}
C
using System;
using System.Text.Json;
class Program
{
static void Main()
{
string[] jsonValues = {
"10",
"1000000000",
"10000000000000001",
"100000000000000000001",
"1" + new string('0', 309),
"10.0",
"10000000000000001.1",
"1.1111111111111111111111111111111111",
"1E2",
"1E309",
};
foreach (string jsonValue in jsonValues)
{
Console.WriteLine($"Testing JSON value: {jsonValue}");
try
{
// Deserialize the JSON value
JsonElement deserialized = JsonSerializer.Deserialize<JsonElement>(jsonValue);
// Check the deserialized type and precision loss
switch (deserialized.ValueKind)
{
case JsonValueKind.Number:
if (deserialized.TryGetInt16(out short smallValue))
{
Console.WriteLine("Deserialized as: short");
}
else if (deserialized.TryGetInt64(out long longValue))
{
Console.WriteLine("Deserialized as: long");
}
else if (deserialized.TryGetDecimal(out decimal decimalValue))
{
Console.WriteLine("Deserialized as: decimal");
string deserializedString = decimalValue.ToString("G29");
if (deserializedString != jsonValue)
{
Console.WriteLine("Precision loss detected!");
Console.WriteLine($"Original value: {jsonValue}");
Console.WriteLine($"Deserialized value: {deserializedString}");
}
}
else
{
Console.WriteLine("Deserialized as: unknown number");
}
break;
default:
Console.WriteLine($"Deserialized as: {deserialized.ValueKind}");
break;
}
// Serialize the value back to JSON
string serialized = JsonSerializer.Serialize(deserialized);
// Check if the serialized value matches the original JSON value
if (serialized != jsonValue)
{
Console.WriteLine("Round-tripping error detected!");
Console.WriteLine($"Original: {jsonValue}");
Console.WriteLine($"Serialized: {serialized}");
}
}
catch (JsonException ex)
{
Console.WriteLine($"Deserialization error: {ex.Message}");
}
Console.WriteLine();
}
}
}
Python(3.8)
import json
import decimal
def test_json_number(number_literal):
print(f"Testing number literal: {number_literal}")
# Deserialize the JSON number
deserialized = json.loads(number_literal)
# Check for precision loss during deserialization
if str(deserialized) != number_literal:
print(" Precision loss during deserialization")
else:
print(" No precision loss during deserialization")
# Serialize the deserialized number back to JSON
serialized = json.dumps(deserialized)
# Check for round-tripping errors
if serialized != number_literal:
print(" Round-tripping error")
else:
print(" No round-tripping error")
print()
# Test the JSON number literals
test_json_number("10")
test_json_number("1000000000")
test_json_number("10000000000000001")
test_json_number("100000000000000000001")
test_json_number("1" + "0" * 4301)
test_json_number("10.0")
test_json_number("10000000000000001.1")
test_json_number("1." + "1" * 34)
test_json_number("1E2")
test_json_number("1E309")
Java(JDK 21, Jackson)
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class NumberTest {
private static final String[] testCases = {
"10",
"1000000000",
"10000000000000001",
"100000000000000000000",
"1" + "0".repeat(309),
"10.0",
"10000000000000001.1",
"1.1111111111111111111111111111111111",
"1E2",
"1E309"
};
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
for (String testCase : testCases) {
System.out.println("Testing JSON value: " + testCase);
try {
// Parse the JSON value
JsonNode jsonNode = objectMapper.readTree(testCase);
// Check the deserialized type and precision loss
if (jsonNode.isInt()) {
System.out.println("Deserialized as: int");
} else if (jsonNode.isLong()) {
System.out.println("Deserialized as: long");
} else if (jsonNode.isBigInteger()) {
System.out.println("Deserialized as: BigInteger");
String deserializedString = jsonNode.bigIntegerValue().toString();
if (!deserializedString.equals(testCase)) {
System.out.println("Precision loss detected!");
System.out.println("Original value
: " + testCase);
System.out.println("Deserialized value: " + deserializedString);
}
} else if (jsonNode.isDouble()) {
System.out.println("Deserialized as: double");
String deserializedString = jsonNode.doubleValue() + "";
if (!deserializedString.equals(testCase)) {
System.out.println("Precision loss detected!");
System.out.println("Original value: " + testCase);
System.out.println("Deserialized value: " + deserializedString);
}
} else if (jsonNode.isDecimal()) {
System.out.println("Deserialized as: BigDecimal");
String deserializedString = jsonNode.decimalValue().toString();
if (!deserializedString.equals(testCase)) {
System.out.println("Precision loss detected!");
System.out.println("Original value: " + testCase);
System.out.println("Deserialized value: " + deserializedString);
}
} else {
System.out.println("Deserialized as: " + jsonNode.getNodeType());
}
// Serialize the value back to JSON
String serialized = objectMapper.writeValueAsString(jsonNode);
// Check if the serialized value matches the original JSON value
if (!serialized.equals(testCase)) {
System.out.println("Round-tripping error detected!");
System.out.println("Original: " + testCase);
System.out.println("Serialized: " + serialized);
}
} catch (Exception e) {
System.out.println("Deserialization error: " + e.getMessage());
}
System.out.println();
}
}
}
Rust
use serde_json::Value;
fn main() {
let json_values = vec![
"10",
"1000000000",
"10000000000000001",
"100000000000000000001",
"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"10.0",
"10000000000000001.1",
"1.1111111111111111111111111111111111",
"1E2",
"1E309",
];
for json_value in json_values {
println!("Testing JSON value: {}", json_value);
// Deserialize the JSON value
let deserialized: Result<Value, _> = serde_json::from_str(json_value);
match deserialized {
Ok(value) => {
// Check the deserialized type and precision loss
match &value {
Value::Number(num) => {
if num.is_i64() {
println!("Deserialized as: i64");
} else if num.is_u64() {
println!("Deserialized as: u64");
} else if num.is_f64() {
println!("Deserialized as: f64");
let deserialized_value = num.as_f64().unwrap().to_string();
if deserialized_value != json_value {
println!("Precision loss detected!");
println!("Original value: {}", json_value);
println!("Deserialized value: {}", deserialized_value);
}
}
}
_ => {
println!("Deserialized as: {:?}", value);
}
}
// Serialize the value back to JSON
let serialized = serde_json::to_string(&value).unwrap();
// Check if the serialized value matches the original JSON value
if serialized != json_value {
println!("Round-tripping error detected!");
println!("Original: {}", json_value);
println!("Serialized: {}", serialized);
}
}
Err(e) => {
println!("Deserialization error: {}", e);
}
}
println!();
}
}
Go
这段代码演示了默认行为:
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
func main() {
testCases := []string{
"10",
"1000000000",
"10000000000000001",
"100000000000000000000",
"1" + strings.Repeat("0", 309),
"10.0",
"10000000000000001.1",
"1.1111111111111111111111111111111111",
"1E2",
"1E309",
}
for _, testCase := range testCases {
fmt.Printf("Testing JSON value: %s\n", testCase)
// Unmarshal the JSON value into a float64
var value float64
err := json.Unmarshal([]byte(testCase), &value)
if err != nil {
fmt.Printf("Deserialization error: %v\n", err)
fmt.Println()
continue
}
fmt.Println("Deserialized as: float64")
// Check for precision loss
deserializedString := strconv.FormatFloat(value, 'g', -1, 64)
if deserializedString != testCase {
fmt.Println("Precision loss detected!")
fmt.Printf("Original value: %s\n", testCase)
fmt.Printf("Deserialized value: %s\n", deserializedString)
}
// Serialize the value back to JSON
serialized, err := json.Marshal(value)
if err != nil {
fmt.Printf("Serialization error: %v\n", err)
fmt.Println()
continue
}
// Check if the serialized value matches the original JSON value
if string(serialized) != testCase {
fmt.Println("Round-tripping error detected!")
fmt.Printf("Original: %s\n", testCase)
fmt.Printf("Serialized: %s\n", string(serialized))
}
fmt.Println()
}
}
这段代码演示了将数据反序列化为已知形状的结构体:
package main
import (
"encoding/json"
"fmt"
"math/big"
"strconv"
"strings"
)
type TestCase struct {
Name string `json:"name"`
Int8 int8 `json:"int8,omitempty"`
Int16 int16 `json:"int16,omitempty"`
Int32 int32 `json:"int32,omitempty"`
Int64 int64 `json:"int64,omitempty"`
Float float64 `json:"float,omitempty"`
}
func main() {
testCases := []string{
`{"name": "Small integer", "int8": 10}`,
`{"name": "Medium integer", "int32": 1000000000}`,
`{"name": "Large integer", "int64": 10000000000000001}`,
`{"name": "Huge integer", "int64": 100000000000000000001}`,
`{"name": "Ridonculous integer", "int64": 1` + strings.Repeat("0", 309) + `}`,
`{"name": "Low-precision decimal", "float": 10.0}`,
`{"name": "High-precision decimal", "float": 10000000000000001.1}`,
`{"name": "Ridonculous-precision decimal", "float": 1.1111111111111111111111111111111111}`,
`{"name": "Small exponential", "float": 1E2}`,
`{"name": "Large exponential", "float": 1E309}`,
}
for _, testCase := range testCases {
var tc TestCase
err := json.Unmarshal([]byte(testCase), &tc)
if err != nil {
fmt.Printf("Deserialization error: %v\n", err)
fmt.Println()
continue
}
fmt.Printf("Testing: %s\n", tc.Name)
// Check the deserialized type and precision loss
switch {
case tc.Int8 != 0:
fmt.Println("Deserialized as: int8")
case tc.Int16 != 0:
fmt.Println("Deserialized as: int16")
case tc.Int32 != 0:
fmt.Println("Deserialized as: int32")
case tc.Int64 != 0:
fmt.Println("Deserialized as: int64")
if tc.Name == "Ridonculous integer" {
bigInt := new(big.Int)
bigInt.SetString(strconv.FormatInt(tc.Int64, 10), 10)
if bigInt.String() != strconv.FormatInt(tc.Int64, 10) {
fmt.Println("Precision loss detected!")
fmt.Printf("Original value: %s\n", strconv.FormatInt(tc.Int64, 10))
fmt.Printf("Deserialized value: %s\n", bigInt.String())
}
}
default:
fmt.Println("Deserialized as: float64")
deserializedString := strconv.FormatFloat(tc.Float, 'g', -1, 64)
if deserializedString != strconv.FormatFloat(tc.Float, 'f', -1, 64) {
fmt.Println("Precision loss detected!")
fmt.Printf("Original value: %s\n", strconv.FormatFloat(tc.Float, 'f', -1, 64))
fmt.Printf("Deserialized value: %s\n", deserializedString)
}
}
// Serialize the value back to JSON
serialized, err := json.Marshal(tc)
if err != nil {
fmt.Printf("Serialization error: %v\n", err)
fmt.Println()
continue
}
// Check if the serialized value matches the original JSON value
var originalTC TestCase
json.Unmarshal([]byte(testCase), &originalTC)
var serializedTC TestCase
json.Unmarshal(serialized, &serializedTC)
if originalTC != serializedTC {
fmt.Println("Round-tripping error detected!")
fmt.Printf("Original: %+v\n", originalTC)
fmt.Printf("Serialized: %+v\n", serializedTC)
}
fmt.Println()
}
}
这种额外限制乍看可能违反了ECMA-404,但实际上只是承认了实现可以自由设置这样的限制的现实。如果不是这样,没有任何JSON解析器可以符合标准,因为它们必须在硬件和软件约束存在的现实中运行。
这个限制曾经要高得多,但后来发现它是一个DoS(拒绝服务)向量,因此Python的较新版本将
int
的字符串长度限制为可怜的4300位数字。可以通过调用sys.set_int_max_str_digits
或设置PYTHONINTMAXSTRDIGITS
环境变量来将其配置为更合理的值。TypeSpec团队提议向格式注册表中添加double-int,以表示可以作为double的整数,这最终将作为
safeint
的输出目标。出于与当前生态系统的兼容性原因,TypeSpec继续使用int64
格式发出safeint
。这很可能解释了我观察到的人们在将
int64
放入线路时不太担心的情况。可怜的JavaScript :(