fix: preserve numerical precision on unary negation (#3242)
This commit is contained in:
1 parent
972772153f
commit
6ce3bdf382
7 files changed
+55
-22
No files matched your search
@@ -373,10 +373,7 @@ sections:
|
|||||||
such `1E1234567890`, precision will be lost if the exponent
|
such `1E1234567890`, precision will be lost if the exponent
|
||||||
is too large.
|
is too large.
|
||||||
|
|
||||||
(3) In jq programs, a leading minus sign will trigger the
|
(3) Comparisons are carried out using the untruncated
|
||||||
conversion of the number to an IEEE754 representation.
|
|
||||||
|
|
||||||
(4) Comparisons are carried out using the untruncated
|
|
||||||
big decimal representation of numbers if available, as
|
big decimal representation of numbers if available, as
|
||||||
illustrated in one of the following examples.
|
illustrated in one of the following examples.
|
||||||
|
|
||||||
@@ -395,15 +392,19 @@ sections:
|
|||||||
input: '0.12345678901234567890123456789'
|
input: '0.12345678901234567890123456789'
|
||||||
output: ['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'
|
input: '12345678909876543212345'
|
||||||
output: ['true']
|
output: ['true']
|
||||||
|
|
||||||
|
- program: '[1234567890987654321,-1234567890987654321 | tojson] == if have_decnum then ["1234567890987654321","-1234567890987654321"] else ["1234567890987654400","-1234567890987654400"] end'
|
||||||
|
input: 'null'
|
||||||
|
output: ['true']
|
||||||
|
|
||||||
- program: '. < 0.12345678901234567890123456788'
|
- program: '. < 0.12345678901234567890123456788'
|
||||||
input: '0.12345678901234567890123456789'
|
input: '0.12345678901234567890123456789'
|
||||||
output: ['false']
|
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]'
|
input: '[1, 1.000, 1.0, 100e-2]'
|
||||||
output: ['true']
|
output: ['true']
|
||||||
|
|
||||||
|
|||||||
Generated
+7
-6
@@ -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\.
|
(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
|
.P
|
||||||
(3) In jq programs, a leading minus sign will trigger the conversion of the number to an IEEE754 representation\.
|
(3) Comparisons are carried out using the untruncated big decimal representation of numbers if available, as illustrated in one of the following examples\.
|
||||||
.
|
|
||||||
.P
|
|
||||||
(4) Comparisons are carried out using the untruncated big decimal representation of numbers if available, as illustrated in one of the following examples\.
|
|
||||||
.
|
.
|
||||||
.P
|
.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\.
|
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
|
||||||
=> 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
|
12345678909876543212345
|
||||||
=> true
|
=> true
|
||||||
|
|
||||||
|
jq \'[1234567890987654321,\-1234567890987654321 | tojson] == if have_decnum then ["1234567890987654321","\-1234567890987654321"] else ["1234567890987654400","\-1234567890987654400"] end\'
|
||||||
|
null
|
||||||
|
=> true
|
||||||
|
|
||||||
jq \'\. < 0\.12345678901234567890123456788\'
|
jq \'\. < 0\.12345678901234567890123456788\'
|
||||||
0\.12345678901234567890123456789
|
0\.12345678901234567890123456789
|
||||||
=> false
|
=> 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]
|
[1, 1\.000, 1\.0, 100e\-2]
|
||||||
=> true
|
=> true
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -256,7 +256,7 @@ static jv f_negate(jq_state *jq, jv input) {
|
|||||||
if (jv_get_kind(input) != JV_KIND_NUMBER) {
|
if (jv_get_kind(input) != JV_KIND_NUMBER) {
|
||||||
return type_error(input, "cannot be negated");
|
return type_error(input, "cannot be negated");
|
||||||
}
|
}
|
||||||
jv ret = jv_number(-jv_number_value(input));
|
jv ret = jv_number_negate(input);
|
||||||
jv_free(input);
|
jv_free(input);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -562,7 +562,6 @@ static decNumber* jvp_dec_number_ptr(jv j) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static jvp_literal_number* jvp_literal_number_alloc(unsigned literal_length) {
|
static jvp_literal_number* jvp_literal_number_alloc(unsigned literal_length) {
|
||||||
|
|
||||||
/* The number of units needed is ceil(DECNUMDIGITS/DECDPUN) */
|
/* The number of units needed is ceil(DECNUMDIGITS/DECDPUN) */
|
||||||
int units = ((literal_length+DECDPUN-1)/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
|
+ sizeof(decNumberUnit) * units
|
||||||
);
|
);
|
||||||
|
|
||||||
|
n->refcnt = JV_REFCNT_INIT;
|
||||||
|
n->num_double = NAN;
|
||||||
|
n->literal_data = NULL;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jv jvp_literal_number_new(const char * literal) {
|
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();
|
decContext *ctx = DEC_CONTEXT();
|
||||||
decContextClearStatus(ctx, DEC_Conversion_syntax);
|
decContextClearStatus(ctx, DEC_Conversion_syntax);
|
||||||
decNumberFromString(&n->num_decimal, literal, ctx);
|
decNumberFromString(&n->num_decimal, literal, ctx);
|
||||||
n->num_double = NAN;
|
|
||||||
|
|
||||||
if (ctx->status & DEC_Conversion_syntax) {
|
if (ctx->status & DEC_Conversion_syntax) {
|
||||||
jv_mem_free(n);
|
jv_mem_free(n);
|
||||||
@@ -734,6 +732,21 @@ int jvp_number_is_nan(jv n) {
|
|||||||
return n.u.number != n.u.number;
|
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) {
|
int jvp_number_cmp(jv a, jv b) {
|
||||||
assert(JVP_HAS_KIND(a, JV_KIND_NUMBER));
|
assert(JVP_HAS_KIND(a, JV_KIND_NUMBER));
|
||||||
assert(JVP_HAS_KIND(b, JV_KIND_NUMBER));
|
assert(JVP_HAS_KIND(b, JV_KIND_NUMBER));
|
||||||
|
|||||||
@@ -74,8 +74,9 @@ jv jv_number(double);
|
|||||||
jv jv_number_with_literal(const char*);
|
jv jv_number_with_literal(const char*);
|
||||||
double jv_number_value(jv);
|
double jv_number_value(jv);
|
||||||
int jv_is_integer(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);
|
const char* jv_number_get_literal(jv);
|
||||||
|
|
||||||
jv jv_array(void);
|
jv jv_array(void);
|
||||||
|
|||||||
@@ -1985,6 +1985,19 @@ true
|
|||||||
{"x":13911860366432393}
|
{"x":13911860366432393}
|
||||||
13911860366432382
|
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 .
|
. |= try . catch .
|
||||||
1
|
1
|
||||||
1
|
1
|
||||||
|
|||||||
Generated
+6
-2
@@ -6,15 +6,19 @@
|
|||||||
0.12345678901234567890123456789
|
0.12345678901234567890123456789
|
||||||
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
|
12345678909876543212345
|
||||||
true
|
true
|
||||||
|
|
||||||
|
[1234567890987654321,-1234567890987654321 | tojson] == if have_decnum then ["1234567890987654321","-1234567890987654321"] else ["1234567890987654400","-1234567890987654400"] end
|
||||||
|
null
|
||||||
|
true
|
||||||
|
|
||||||
. < 0.12345678901234567890123456788
|
. < 0.12345678901234567890123456788
|
||||||
0.12345678901234567890123456789
|
0.12345678901234567890123456789
|
||||||
false
|
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]
|
[1, 1.000, 1.0, 100e-2]
|
||||||
true
|
true
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user