Skip to content

Commit 20a5e0a

Browse files
committed
fix: preserve numerical precision on unary negation
1 parent d0adcbf commit 20a5e0a

File tree

7 files changed

+55
-24
lines changed

7 files changed

+55
-24
lines changed

docs/content/manual/dev/manual.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,7 @@ sections:
373373
such `1E1234567890`, precision will be lost if the exponent
374374
is too large.
375375
376-
(3) In jq programs, a leading minus sign will trigger the
377-
conversion of the number to an IEEE754 representation.
378-
379-
(4) Comparisons are carried out using the untruncated
376+
(3) Comparisons are carried out using the untruncated
380377
big decimal representation of numbers if available, as
381378
illustrated in one of the following examples.
382379
@@ -395,15 +392,19 @@ sections:
395392
input: '0.12345678901234567890123456789'
396393
output: ['0.12345678901234567890123456789']
397394

398-
- program: '[., tojson] | . == if have_decnum then [12345678909876543212345,"12345678909876543212345"] else [12345678909876543000000,"12345678909876543000000"] end'
395+
- program: '[., tojson] == if have_decnum then [12345678909876543212345,"12345678909876543212345"] else [12345678909876543000000,"12345678909876543000000"] end'
399396
input: '12345678909876543212345'
400397
output: ['true']
401398

399+
- program: '[1234567890987654321,-1234567890987654321 | tojson] == if have_decnum then ["1234567890987654321","-1234567890987654321"] else ["1234567890987654400","-1234567890987654400"] end'
400+
input: 'null'
401+
output: ['true']
402+
402403
- program: '. < 0.12345678901234567890123456788'
403404
input: '0.12345678901234567890123456789'
404405
output: ['false']
405406

406-
- 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'
407+
- 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'
407408
input: '[1, 1.000, 1.0, 100e-2]'
408409
output: ['true']
409410

jq.1.prebuilt

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/builtin.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ static jv f_negate(jq_state *jq, jv input) {
256256
if (jv_get_kind(input) != JV_KIND_NUMBER) {
257257
return type_error(input, "cannot be negated");
258258
}
259-
jv ret = jv_number(-jv_number_value(input));
259+
jv ret = jv_number_negate(input);
260260
jv_free(input);
261261
return ret;
262262
}

src/jv.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,6 @@ static decNumber* jvp_dec_number_ptr(jv j) {
562562
}
563563

564564
static jvp_literal_number* jvp_literal_number_alloc(unsigned literal_length) {
565-
566565
/* The number of units needed is ceil(DECNUMDIGITS/DECDPUN) */
567566
int units = ((literal_length+DECDPUN-1)/DECDPUN);
568567

@@ -571,27 +570,25 @@ static jvp_literal_number* jvp_literal_number_alloc(unsigned literal_length) {
571570
+ sizeof(decNumberUnit) * units
572571
);
573572

573+
n->refcnt = JV_REFCNT_INIT;
574+
n->num_double = NAN;
575+
n->literal_data = NULL;
574576
return n;
575577
}
576578

577579
static jv jvp_literal_number_new(const char * literal) {
580+
jvp_literal_number* n = jvp_literal_number_alloc(strlen(literal));
578581

579-
jvp_literal_number * n = jvp_literal_number_alloc(strlen(literal));
580-
581-
n->refcnt = JV_REFCNT_INIT;
582-
n->literal_data = NULL;
583582
decContext *ctx = DEC_CONTEXT();
584583
decContextClearStatus(ctx, DEC_Conversion_syntax);
585584
decNumberFromString(&n->num_decimal, literal, ctx);
586-
n->num_double = NAN;
587585

588586
if (ctx->status & DEC_Conversion_syntax) {
589587
jv_mem_free(n);
590588
return JV_INVALID;
591589
}
592590

593-
jv r = {JVP_FLAGS_NUMBER_LITERAL, 0, 0, JV_NUMBER_SIZE_INIT, {&n->refcnt}};
594-
return r;
591+
return (jv){JVP_FLAGS_NUMBER_LITERAL, 0, 0, JV_NUMBER_SIZE_INIT, {&n->refcnt}};
595592
}
596593

597594
static double jvp_literal_number_to_double(jv j) {
@@ -734,6 +731,20 @@ int jvp_number_is_nan(jv n) {
734731
return n.u.number != n.u.number;
735732
}
736733

734+
jv jv_number_negate(jv n) {
735+
assert(JVP_HAS_KIND(n, JV_KIND_NUMBER));
736+
737+
#ifdef USE_DECNUM
738+
if (JVP_HAS_FLAGS(n, JVP_FLAGS_NUMBER_LITERAL)) {
739+
jvp_literal_number* m = jvp_literal_number_alloc(jvp_dec_number_ptr(n)->digits);
740+
741+
decNumberMinus(&m->num_decimal, jvp_dec_number_ptr(n), DEC_CONTEXT());
742+
return (jv){JVP_FLAGS_NUMBER_LITERAL, 0, 0, JV_NUMBER_SIZE_INIT, {&m->refcnt}};
743+
}
744+
#endif
745+
return jv_number(-jv_number_value(n));
746+
}
747+
737748
int jvp_number_cmp(jv a, jv b) {
738749
assert(JVP_HAS_KIND(a, JV_KIND_NUMBER));
739750
assert(JVP_HAS_KIND(b, JV_KIND_NUMBER));

src/jv.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ jv jv_number(double);
7474
jv jv_number_with_literal(const char*);
7575
double jv_number_value(jv);
7676
int jv_is_integer(jv);
77+
jv jv_number_negate(jv);
7778

78-
int jv_number_has_literal(jv n);
79+
int jv_number_has_literal(jv);
7980
const char* jv_number_get_literal(jv);
8081

8182
jv jv_array(void);

tests/jq.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,19 @@ true
19851985
{"x":13911860366432393}
19861986
13911860366432382
19871987

1988+
# Unary negation preserves numerical precision
1989+
-. | tojson == if have_decnum then "-13911860366432393" else "-13911860366432392" end
1990+
13911860366432393
1991+
true
1992+
1993+
-. | tojson == if have_decnum then "0.12345678901234567890123456789" else "0.12345678901234568" end
1994+
-0.12345678901234567890123456789
1995+
true
1996+
1997+
[1E+1000,-1E+1000 | tojson] == if have_decnum then ["1E+1000","-1E+1000"] else ["1.7976931348623157e+308","-1.7976931348623157e+308"] end
1998+
null
1999+
true
2000+
19882001
. |= try . catch .
19892002
1
19902003
1

tests/man.test

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)