Repository: jqlang/jq

Problem statement:
Inconsistent dump results of jq
Hi there,

I found that for large floating numbers, when compiling with **gcc** and **clang**, `jq` would emit different outputs.

For example, for `echo '{"a": 1E9999999999}' | jq .`, 
**gcc** compiled `jq` would emit: 
```
{
  "a": ((double)1.79769313486231570814527423731704357e+308L)
}
```
while **clang** compiled `jq` would emit:
```
{
  "a": 1.7976931348623157e+308
}
```

I think the cause is probably different interpretation of `DBL_MAX` / `DBL_MIN` in gcc and clang. But to ensure the correctness, robustness, and consistency of `jq`, it's important to keep programs' behaviour consistent across main-stream compilers.

Inconsistent dump results of jq
Hi there,

I found that for large floating numbers, when compiling with **gcc** and **clang**, `jq` would emit different outputs.

For example, for `echo '{"a": 1E9999999999}' | jq .`, 
**gcc** compiled `jq` would emit: 
```
{
  "a": ((double)1.79769313486231570814527423731704357e+308L)
}
```
while **clang** compiled `jq` would emit:
```
{
  "a": 1.7976931348623157e+308
}
```

I think the cause is probably different interpretation of `DBL_MAX` / `DBL_MIN` in gcc and clang. But to ensure the correctness, robustness, and consistency of `jq`, it's important to keep programs' behaviour consistent across main-stream compilers.



Hints:
Reproduced with debian 12 and gcc:
```sh
root@6eeb8d61a151:/Users/wader/src/jq# gcc --version
gcc (Debian 12.2.0-14) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

root@6eeb8d61a151:/Users/wader/src/jq# echo '{"a": 1E999999999}' | ./jq
{
  "a": 1E+999999999
}
root@6eeb8d61a151:/Users/wader/src/jq# echo '{"a": 1E9999999999}' | ./jq
{
  "a": ((double)1.79769313486231570814527423731704357e+308L)
}
```
Seems to boil down to this difference:
```sh
root@6eeb8d61a151:/Users/wader/src/jq# echo -e '#include <float.h>\n#define STR(x) #x\n#define XSTR(x) STR(x)\nXSTR(DBL_MAX) DBL_MAX __DBL_MAX__' | clang -E -
# 1 "<stdin>"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 361 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "<stdin>" 2
# 1 "/usr/lib/llvm-14/lib/clang/14.0.6/include/float.h" 1 3
# 2 "<stdin>" 2


"1.7976931348623157e+308" 1.7976931348623157e+308 1.7976931348623157e+308
root@6eeb8d61a151:/Users/wader/src/jq# echo -e '#include <float.h>\n#define STR(x) #x\n#define XSTR(x) STR(x)\nXSTR(DBL_MAX) DBL_MAX __DBL_MAX__' | cpp
# 0 "<stdin>"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "<stdin>"
# 1 "/usr/lib/gcc/x86_64-linux-gnu/12/include/float.h" 1 3 4
# 2 "<stdin>" 2


"((double)1.79769313486231570814527423731704357e+308L)" ((double)1.79769313486231570814527423731704357e+308L) ((double)1.79769313486231570814527423731704357e+308L)
```
`DBL_MAX_STR` is `XSTR(DBL_MAX)` and is used here https://github.com/jqlang/jq/blob/master/src/jv.c#L665
`__DBL_MAX__` i think is defined by the compiler and is different.


I wonder what the backwards compatibiltiy is about at https://github.com/jqlang/jq/blob/master/src/jv.c#L660-L667 to preserve how `infinite/0` is represented etc? was added as part of https://github.com/jqlang/jq/pull/1752 and there are some tests about being compatible but not sure they made me wiser, maybe @leonid-s-usov remembers?

Otherwise i guess we could just skip the `decNumberIsInfinite` check and it will return `plit->literal_data`?

Other alternatives could be to runtime string format the constants at startup or maybe define our own min/max constants somehow?
Removing these statements outputs `Infinity` (not JSON!), not the input literal `1E9999999999`.
That's not good. So that is the case when the decNumber code thinks it's too large even
Reproduced with debian 12 and gcc:
```sh
root@6eeb8d61a151:/Users/wader/src/jq# gcc --version
gcc (Debian 12.2.0-14) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

root@6eeb8d61a151:/Users/wader/src/jq# echo '{"a": 1E999999999}' | ./jq
{
  "a": 1E+999999999
}
root@6eeb8d61a151:/Users/wader/src/jq# echo '{"a": 1E9999999999}' | ./jq
{
  "a": ((double)1.79769313486231570814527423731704357e+308L)
}
```
Seems to boil down to this difference:
```sh
root@6eeb8d61a151:/Users/wader/src/jq# echo -e '#include <float.h>\n#define STR(x) #x\n#define XSTR(x) STR(x)\nXSTR(DBL_MAX) DBL_MAX __DBL_MAX__' | clang -E -
# 1 "<stdin>"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 361 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "<stdin>" 2
# 1 "/usr/lib/llvm-14/lib/clang/14.0.6/include/float.h" 1 3
# 2 "<stdin>" 2


"1.7976931348623157e+308" 1.7976931348623157e+308 1.7976931348623157e+308
root@6eeb8d61a151:/Users/wader/src/jq# echo -e '#include <float.h>\n#define STR(x) #x\n#define XSTR(x) STR(x)\nXSTR(DBL_MAX) DBL_MAX __DBL_MAX__' | cpp
# 0 "<stdin>"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "<stdin>"
# 1 "/usr/lib/gcc/x86_64-linux-gnu/12/include/float.h" 1 3 4
# 2 "<stdin>" 2


"((double)1.79769313486231570814527423731704357e+308L)" ((double)1.79769313486231570814527423731704357e+308L) ((double)1.79769313486231570814527423731704357e+308L)
```
`DBL_MAX_STR` is `XSTR(DBL_MAX)` and is used here https://github.com/jqlang/jq/blob/master/src/jv.c#L665
`__DBL_MAX__` i think is defined by the compiler and is different.


I wonder what the backwards compatibiltiy is about at https://github.com/jqlang/jq/blob/master/src/jv.c#L660-L667 to preserve how `infinite/0` is represented etc? was added as part of https://github.com/jqlang/jq/pull/1752 and there are some tests about being compatible but not sure they made me wiser, maybe @leonid-s-usov remembers?

Otherwise i guess we could just skip the `decNumberIsInfinite` check and it will return `plit->literal_data`?

Other alternatives could be to runtime string format the constants at startup or maybe define our own min/max constants somehow?
Removing these statements outputs `Infinity` (not JSON!), not the input literal `1E9999999999`.
That's not good. So that is the case when the decNumber code thinks it's too large even

Patch:
diff --git a/src/jv.c b/src/jv.c
index e1fb209f34..6de4150727 100644
--- a/src/jv.c
+++ b/src/jv.c
@@ -216,16 +216,11 @@ enum {
 #define JVP_FLAGS_NUMBER_NATIVE_STR   JVP_MAKE_FLAGS(JV_KIND_NUMBER, JVP_MAKE_PFLAGS(JVP_NUMBER_NATIVE, 1))
 #define JVP_FLAGS_NUMBER_LITERAL      JVP_MAKE_FLAGS(JV_KIND_NUMBER, JVP_MAKE_PFLAGS(JVP_NUMBER_DECIMAL, 1))
 
-#define STR(x) #x
-#define XSTR(x) STR(x)
-#define DBL_MAX_STR XSTR(DBL_MAX)
-#define DBL_MIN_STR "-" XSTR(DBL_MAX)
-
 // the decimal precision of binary double
 #define BIN64_DEC_PRECISION  (17)
 #define DEC_NUMBER_STRING_GUARD (14)
 
-#include <jv_thread.h>
+#include "jv_thread.h"
 #ifdef WIN32
 /* Copied from Heimdal: thread-specific keys; see lib/base/dll.c in Heimdal */
 
@@ -658,12 +653,12 @@ static const char* jvp_literal_number_literal(jv n) {
   }
 
   if (decNumberIsInfinite(pdec)) {
-    // For backward compatibility.
-    if (decNumberIsNegative(pdec)) {
-      return DBL_MIN_STR;
-    } else {
-      return DBL_MAX_STR;
-    }
+    // We cannot preserve the literal data of numbers outside the limited
+    // range of exponent. Since `decNumberToString` returns "Infinity"
+    // (or "-Infinity"), and to reduce stack allocations as possible, we
+    // normalize infinities in the callers instead of printing the maximum
+    // (or minimum) double here.
+    return NULL;
   }
 
   if (plit->literal_data == NULL) {


Test patch:
diff --git a/tests/shtest b/tests/shtest
index 369cfdb4ad..8af791ae2e 100755
--- a/tests/shtest
+++ b/tests/shtest
@@ -152,6 +152,12 @@ elif [ $? -ne 5 ]; then
   exit 1
 fi
 
+# Regression test for #2367; make sure to call jq twice
+if ! echo '{"a": 1E9999999999}' | $JQ . | $JQ -e .a; then
+  printf 'Issue #2367 is back?\n' 1>&2
+  exit 1
+fi
+
 # Regression test for #1534
 echo "[1,2,3,4]" > $d/expected
 printf "[1,2][3,4]" | $JQ -cs add > $d/out 2>&1