为什么要在 JSON 中使用字符串来表示十进制数



某些 API(如 PayPal API)使用 JSON 中的字符串类型来表示十进制数。所以"7.47"而不是7.47.

为什么/何时使用json数字值类型是一个好主意?AFAIK数字值类型允许无限精度以及科学记数法。

将 JSON 中的数值作为字符串传输的主要原因是消除传输过程中的任何精度损失或歧义。

确实,JSON 规范没有指定数值的精度。这并不意味着 JSON 数字具有无限的精度。这意味着未指定数值精度,这意味着 JSON 实现可以自由选择任何方便其实现或目标的数字精度。 如果您的应用具有特定的精度要求,这种可变性可能会很痛苦。

精度损失在数值的 JSON 编码中通常不明显(1.7 很好且简洁),但在接收端的 JSON 解析和中间表示中表现出来。 JSON 解析函数可以非常合理地将 1.7 解析为 IEEE 双精度浮点数。但是,有限长度/有限精度十进制表示将始终遇到十进制扩展不能表示为有限数字序列的数字:

  1. 无理数(如 pi 和 e)

  2. 1.7 以 10 为基数表示法具有
  3. 有限表示形式,但在二进制(以 2 为基数)表示法中,1.7 无法精确编码。即使有近乎无限数量的二进制数字,你也只能接近 1.7,但你永远不会精确地达到 1.7。

因此,将 1.7

解析为内存中的浮点数,然后打印出该数字可能会返回类似于 1.69 的内容,而不是 1.7。

JSON 1.7 值的使用者可以使用更复杂的技术来解析并将值保留在内存中,例如使用定点数据类型或具有任意精度的"字符串 int"数据类型,但这并不能完全消除某些数字转换精度损失的幽灵。 现实情况是,很少有JSON解析器会采取这种极端措施,因为在大多数情况下的好处很低,内存和CPU成本很高。

因此,如果要向使用者发送精确的数值,并且不希望将值自动转换为典型的内部数值表示形式,则最好的办法是将数值作为字符串发送出去,并准确地告诉使用者在需要对其执行数值操作时应如何处理该字符串。

例如:在一些JSON生产者(JRuby)中,BigInteger值自动作为字符串输出到JSON,主要是因为BigInteger的范围和精度比IEEE双精度浮点数大得多。将 BigInteger 值减少到双精度以便输出为 JSON 数字通常会丢失有效数字。

此外,JSON 规范 (http://www.json.org/) 明确指出 NaN 和无穷大 (INF) 对于 JSON 数值无效。 如果需要表达这些边缘元素,则不能使用 JSON 编号。您必须使用字符串或对象结构。

最后,还有另一个方面可能导致选择将数字数据作为字符串发送:控制显示格式。 前导零和尾随零对数值无关紧要。如果发送 JSON 数字值 2.10 或 004,则转换为内部数字形式后,它们将显示为 2.1 和 4。

如果您要发送将直接显示给用户的数据,您可能希望您的货币数字在屏幕上很好地排列,十进制对齐。一种方法是让客户端负责格式化要显示的数据。 另一种方法是让服务器格式化要显示的数据。客户端在屏幕上显示内容可能更简单,但如果客户端还需要对值进行计算,这可能会使从字符串中提取数值变得困难。

我会有点逆向,并说7.47在 JSON 中是完全安全的,即使是财务金额,而且"7.47"并不更安全。

<小时 />

首先,让我解决这个线程中的一些误解:

因此,将 1.7

解析为内存中的浮点数,然后打印出该数字可能会返回类似于 1.69 的内容,而不是 1.7。

这是不正确的,特别是在该答案中提到的IEEE 754双精度格式的上下文中。 1.7 转换为精确的双精度 1.69999999999999999555910790149937383830547332763671875 当该值"打印"用于显示时,它将始终为 1.7,而不是 1.69、1.699999999999 或 1.70000000001。它是 1.7"完全"。

在此处了解更多信息。

7.47 转换为浮点数时实际上可能是 7.46999999923423423423

7.47 已经是一个浮点数,具有精确的双精度值 7.46999999999999975131004248396493494510650634765625。它不会被"转换"为任何其他浮点数。

一个简单的系统,简单地截断多余的数字将导致7.46,现在你在某个地方损失了一分钱

IEEE轮,而不是修剪。首先,它不会转换为 7.47 以外的任何其他数字。

JSON 编号实际上是浮点数吗?据我了解,这是一个与语言无关的数字,如果愿意,您可以将 JSON 数字直接解析为 java BigDecimal 或其他任何语言的任意精度格式。

建议将 JSON 数字解释为双精度(IEEE 754 双精度格式)。我还没有看到一个解析器不会这样做。

不,BigDecimal(7.47)不是正确的方法——它实际上会创建一个 BigDecimal 来表示 7.47 的精确双精度,即 7.469999999999999975131004248396493494510650634765625。若要获得预期的行为,应使用BigDecimal("7.47")

<小时 />

总的来说,我认为{"price": 7.47}没有任何根本问题。它将在几乎所有平台上转换为双精度,IEEE 754 的语义保证它将准确且始终地"打印"为 7.47。

当然,浮点舍入错误可能会发生在使用该值的进一步计算中,例如 0.1 + 0.2 == 0.300000000000000004,但我看不出 JSON 中的字符串如何使它变得更好。如果"7.47"作为字符串到达并且应该是某些计算的一部分,则无论如何都需要将其转换为某种数字数据类型,可能是浮:)。

值得注意的是,字符串也有缺点,例如,它们不能传递给Intl.NumberFormat,它们不是"纯"数据类型,例如,点是格式决定。

我并不强烈反对字符串,它们对我来说似乎也很好,但我也没有看到{"price": 7.47}有什么问题。

我这样做的原因是SoftwareAG解析器试图从它收到的值中"猜测"java类型。

所以当它收到

"jackpot":{
 "growth":200,
 "percentage":66.67
}

第一个值(增长)将成为java.lang.Long第二个值(百分比)将成为java.lang.Double

现在,当这个头奖数组中的第二个对象具有此

"jackpot":{
 "growth":50.50,
 "percentage":65
}

我有一个问题。

当我将这些值交换为字符串时,我可以完全控制并且可以将值转换为我想要的任何值。

摘要版本

只是引用@dthorpe的回答,因为我认为这是最重要的一点:

此外,JSON 规范 (http://www.json.org/) 明确指出 NaN 和无穷大 (INF) 对于 JSON 数值无效。如果需要表达这些边缘元素,则不能使用 JSON 编号。您必须使用字符串或对象结构。

I18N 是不使用 String 作为十进制数的另一个原因

在几十个国家,如德国和法国,逗号(,)是小数点分隔符,点(.)是千位分隔符。请参阅维基百科上的列表。

如果您的 JSON 文档string带有十进制数字,则您依赖于所有可能的 API 使用者使用相同的数字格式转换(这是 JSON 解析之后的一个步骤)。由于反转使用逗号和点作为分隔符,存在错误转换的风险。

如果对十进制数使用number,则可以避免风险。

意见

  1. 精度:通常是红鲱鱼
  2. 歧义:通常是红鲱鱼

请参阅numberhttps://www.json.org/json-en.html 并使用符合规则

仅当值可能挑战 IEEE 754 双精度(大约 15 位有效数字)的限制时,才指定字符串,因为您知道您没有使用"非常像 C 或 Java 数字的数字",并在发布 API 时明确说明这一点。我总是希望"几乎正好是 1.7"的double准确地转换为 decimal 1.7,而不会跳过额外的箍(只有值可能有>~ 15 位有效数字时才例外)。

  1. 格式:
  • 提供值(数据交换的任何一方都可以在计算中使用)作为数字
  • 将信息显示为字符串("2023-07-01"、"$ 1,234.57"),不要将两种用途混合使用
  • (接收number的客户端可以 - 显然 - 按照它选择的格式)
  1. PayPal示例:
    一个写得体面的客户端不应该(必须)关心(C# Convert.ToDouble(jsonValue) /* or ToDecimal */处理"7.47"7.47是一样的)。

最后1:虽然我更喜欢在生成值时"考虑JSON number",但我不在乎规定其他人应该做什么。我最近不得不使用 IBM CICS Web Services 与大型机 COBOL 系统进行交互,并以带有十进制逗号而不是点"1234,57"的字符串形式获取数字。两种选择:告诉服务所有者更改服务以适应我对它应该如何看待或适应的宗教观点,以使用我现在所知道的关于该系统如何工作的知识......选择是显而易见的。

最后 2:知道并预先指定当出现非数字结果时该怎么做(当计算产生 INF 或 NaN 时应该怎么做)。如果您真的真的需要传递"+INF"作为有效结果,那么我会说您有使用字符串的情况 - 再次必须显式指定行为(以免您的代码中断或破坏另一端的正常反序列化 - 这里没有商定的标准,您不能假设"INF"将反序列化为包含+INFdouble/decimal)。

最新更新