1
0
mirror of https://github.com/jqlang/jq.git synced 2025-04-18 17:24:01 +03:00

fix: preserve numerical precision on unary negation (#3242)

This commit is contained in:
itchyny 2025-02-06 07:50:21 +09:00 committed by GitHub
parent 972772153f
commit 6ce3bdf382
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 55 additions and 22 deletions

View File

@ -373,10 +373,7 @@ sections:
such `1E1234567890`, precision will be lost if the exponent
is too large.
(3) In jq programs, a leading minus sign will trigger the
conversion of the number to an IEEE754 representation.
(4) Comparisons are carried out using the untruncated
(3) Comparisons are carried out using the untruncated
big decimal representation of numbers if available, as
illustrated in one of the following examples.
@ -395,15 +392,19 @@ sections:
input: '0.12345678901234567890123456789'
output: ['0.12345678901234567890123456789']
- program: '[., tojson] | . == if have_decnum then [12345678909876543212345,"12345678909876543212345"] else [12345678909876543000000,"12345678909876543000000"] end'
- program: '[., tojson] == if have_decnum then [12345678909876543212345,"12345678909876543212345"] else [12345678909876543000000,"12345678909876543000000"] end'
input: '12345678909876543212345'
output: ['true']
- program: '[1234567890987654321,-1234567890987654321 | tojson] == if have_decnum then ["1234567890987654321","-1234567890987654321"] else ["1234567890987654400","-1234567890987654400"] end'
input: 'null'
output: ['true']
- program: '. < 0.12345678901234567890123456788'
input: '0.12345678901234567890123456789'
output: ['false']
- program: 'map([., . == 1]) | tojson | . == if have_decnum then "[[1,true],[1.000,true],[1.0,true],[1.00,true]]" else "[[1,true],[1,true],[1,true],[1,true]]" end'
- program: 'map([., . == 1]) | tojson == if have_decnum then "[[1,true],[1.000,true],[1.0,true],[1.00,true]]" else "[[1,true],[1,true],[1,true],[1,true]]" end'
input: '[1, 1.000, 1.0, 100e-2]'
output: ['true']

13
jq.1.prebuilt generated
View File

@ -298,10 +298,7 @@ The following remarks are therefore offered with the understanding that they are
(2) jq will attempt to maintain the original decimal precision of number literals (if the \fB\-\-disable\-decnum\fR build configuration option was not used), but in expressions such \fB1E1234567890\fR, precision will be lost if the exponent is too large\.
.
.P
(3) In jq programs, a leading minus sign will trigger the conversion of the number to an IEEE754 representation\.
.
.P
(4) Comparisons are carried out using the untruncated big decimal representation of numbers if available, as illustrated in one of the following examples\.
(3) Comparisons are carried out using the untruncated big decimal representation of numbers if available, as illustrated in one of the following examples\.
.
.P
The examples below use the builtin function \fBhave_decnum\fR in order to demonstrate the expected effects of using / not using the \fB\-\-disable\-decnum\fR build configuration option, and also to allow automated tests derived from these examples to pass regardless of whether that option is used\.
@ -318,15 +315,19 @@ jq \'\.\'
0\.12345678901234567890123456789
=> 0\.12345678901234567890123456789
jq \'[\., tojson] | \. == if have_decnum then [12345678909876543212345,"12345678909876543212345"] else [12345678909876543000000,"12345678909876543000000"] end\'
jq \'[\., tojson] == if have_decnum then [12345678909876543212345,"12345678909876543212345"] else [12345678909876543000000,"12345678909876543000000"] end\'
12345678909876543212345
=> true
jq \'[1234567890987654321,\-1234567890987654321 | tojson] == if have_decnum then ["1234567890987654321","\-1234567890987654321"] else ["1234567890987654400","\-1234567890987654400"] end\'
null
=> true
jq \'\. < 0\.12345678901234567890123456788\'
0\.12345678901234567890123456789
=> false
jq \'map([\., \. == 1]) | tojson | \. == if have_decnum then "[[1,true],[1\.000,true],[1\.0,true],[1\.00,true]]" else "[[1,true],[1,true],[1,true],[1,true]]" end\'
jq \'map([\., \. == 1]) | tojson == if have_decnum then "[[1,true],[1\.000,true],[1\.0,true],[1\.00,true]]" else "[[1,true],[1,true],[1,true],[1,true]]" end\'
[1, 1\.000, 1\.0, 100e\-2]
=> true

View File

@ -256,7 +256,7 @@ static jv f_negate(jq_state *jq, jv input) {
if (jv_get_kind(input) != JV_KIND_NUMBER) {
return type_error(input, "cannot be negated");
}
jv ret = jv_number(-jv_number_value(input));
jv ret = jv_number_negate(input);
jv_free(input);
return ret;
}

View File

@ -562,7 +562,6 @@ static decNumber* jvp_dec_number_ptr(jv j) {
}
static jvp_literal_number* jvp_literal_number_alloc(unsigned literal_length) {
/* The number of units needed is ceil(DECNUMDIGITS/DECDPUN) */
int units = ((literal_length+DECDPUN-1)/DECDPUN);
@ -571,19 +570,18 @@ static jvp_literal_number* jvp_literal_number_alloc(unsigned literal_length) {
+ sizeof(decNumberUnit) * units
);
n->refcnt = JV_REFCNT_INIT;
n->num_double = NAN;
n->literal_data = NULL;
return n;
}
static jv jvp_literal_number_new(const char * literal) {
jvp_literal_number* n = jvp_literal_number_alloc(strlen(literal));
jvp_literal_number * n = jvp_literal_number_alloc(strlen(literal));
n->refcnt = JV_REFCNT_INIT;
n->literal_data = NULL;
decContext *ctx = DEC_CONTEXT();
decContextClearStatus(ctx, DEC_Conversion_syntax);
decNumberFromString(&n->num_decimal, literal, ctx);
n->num_double = NAN;
if (ctx->status & DEC_Conversion_syntax) {
jv_mem_free(n);
@ -734,6 +732,21 @@ int jvp_number_is_nan(jv n) {
return n.u.number != n.u.number;
}
jv jv_number_negate(jv n) {
assert(JVP_HAS_KIND(n, JV_KIND_NUMBER));
#ifdef USE_DECNUM
if (JVP_HAS_FLAGS(n, JVP_FLAGS_NUMBER_LITERAL)) {
jvp_literal_number* m = jvp_literal_number_alloc(jvp_dec_number_ptr(n)->digits);
decNumberMinus(&m->num_decimal, jvp_dec_number_ptr(n), DEC_CONTEXT());
jv r = {JVP_FLAGS_NUMBER_LITERAL, 0, 0, 0, {&m->refcnt}};
return r;
}
#endif
return jv_number(-jv_number_value(n));
}
int jvp_number_cmp(jv a, jv b) {
assert(JVP_HAS_KIND(a, JV_KIND_NUMBER));
assert(JVP_HAS_KIND(b, JV_KIND_NUMBER));

View File

@ -74,8 +74,9 @@ jv jv_number(double);
jv jv_number_with_literal(const char*);
double jv_number_value(jv);
int jv_is_integer(jv);
jv jv_number_negate(jv);
int jv_number_has_literal(jv n);
int jv_number_has_literal(jv);
const char* jv_number_get_literal(jv);
jv jv_array(void);

View File

@ -1985,6 +1985,19 @@ true
{"x":13911860366432393}
13911860366432382
# Unary negation preserves numerical precision
-. | tojson == if have_decnum then "-13911860366432393" else "-13911860366432392" end
13911860366432393
true
-. | tojson == if have_decnum then "0.12345678901234567890123456789" else "0.12345678901234568" end
-0.12345678901234567890123456789
true
[1E+1000,-1E+1000 | tojson] == if have_decnum then ["1E+1000","-1E+1000"] else ["1.7976931348623157e+308","-1.7976931348623157e+308"] end
null
true
. |= try . catch .
1
1

8
tests/man.test generated
View File

@ -6,15 +6,19 @@
0.12345678901234567890123456789
0.12345678901234567890123456789
[., tojson] | . == if have_decnum then [12345678909876543212345,"12345678909876543212345"] else [12345678909876543000000,"12345678909876543000000"] end
[., tojson] == if have_decnum then [12345678909876543212345,"12345678909876543212345"] else [12345678909876543000000,"12345678909876543000000"] end
12345678909876543212345
true
[1234567890987654321,-1234567890987654321 | tojson] == if have_decnum then ["1234567890987654321","-1234567890987654321"] else ["1234567890987654400","-1234567890987654400"] end
null
true
. < 0.12345678901234567890123456788
0.12345678901234567890123456789
false
map([., . == 1]) | tojson | . == if have_decnum then "[[1,true],[1.000,true],[1.0,true],[1.00,true]]" else "[[1,true],[1,true],[1,true],[1,true]]" end
map([., . == 1]) | tojson == if have_decnum then "[[1,true],[1.000,true],[1.0,true],[1.00,true]]" else "[[1,true],[1,true],[1,true],[1,true]]" end
[1, 1.000, 1.0, 100e-2]
true