jq コマンドに大きな数値を与えると丸め誤差が発生する!

はじめに

こんにちは、川原です。

ここ最近ずっと、JSON をレスポンスするAPIサーバープログラムの開発に携わっており、JSON と戯れる日々を送っています。 そうなると、JSON レスポンスの確認するために jq をよく使うことになるのですが、先日、jq 起因のとある事象にハマりましたので、その事象を共有します。

結論

まず、結論ですが、

jq に大きな数値を与えると、「丸め誤差」が発生します。

以下のように Java の Long.MAX_VALUE 値である 9223372036854775807jq に与えてみると、、、

>jq -V
jq-1.5

>echo '{ "BigInteger": 9223372036854775807 }' | jq .
{
  "BigInteger": 9223372036854776000
}

下4桁の部分で丸められて(切り上げられて)しまいます。

上記例のように、数値を jq に与え、jq の出力をそのまま確認している分には、想定外の値が出力される問題事象の原因が jq であることにすぐに気付けます。

が、私の場合、APIサーバーの出力内容を jq 経由で確認している際に、この事象(想定と異なる数値の出力)に遭遇しました。そのため、APIサーバー側の処理の中で数値を丸めてJSONとして出力しているのではないかと、APIサーバー側のプログラムソースコードをずっと確認してしまいました。。。(;^_^
(jq の処理は与えられた JSON を単純に整形して出力するだけ、という思い込みもありました)

本事象について jq の開発元の情報を確認してみると、ちゃんと記載されていました。

https://github.com/stedolan/jq/wiki/FAQ#numbers

Q: What are the largest (smallest) numbers that jq can handle? Does "overflow" cause an error?

A: Currently, jq does not include "bigint" support. Very large integers will be converted to a floating point approximation. The largest number that can be reliably used as an integer is 2^53 (9,007,199,254,740,992). The largest floating point value is about 1.79e+308 and the smallest is about 1e-323.

In general, arithmetic operations do not raise errors, except that in jq 1.5, division by 0 does result in an error. In jq 1.4, 1/0 prints out as 1.7976931348623157e+308, and 0 * (1/0) evaluates to null.

A basic "bigint" library is available at Bigint.jq.

上記のように jq では、整数値は 2^53 (9,007,199,254,740,992) までしか扱えないようです。

ということで、jq に大きな数値を与えるときは気をつけましょう。 また、APIサーバーの JSON レスポンスが想定外の値である場合は、jq を介さない素のレスポンスも確認しましょう。