-
Notifications
You must be signed in to change notification settings - Fork 913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wire: remove towire_double() #3535
wire: remove towire_double() #3535
Conversation
OK, I prefer to attack the root cause here, which is to remove json_to_double (and the unused json_add_double) and all users.
|
619a3d6
to
cc73b12
Compare
Done, except that I left |
common/json_tok.c
Outdated
*num = tal(cmd, double); | ||
if (json_to_double(buffer, tok, *num)) | ||
double d; | ||
if (json_to_double(buffer, tok, &d) && d >= 0.0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My discomfort with json_to_double is that it uses strtod, which tries to accept just about anything. This means what actually happens is that it's machine-specific.
I'd be more comfortable with a json_to_millionths (which, actually was surprisingly nontrivial to code up):
diff --git a/common/json.c b/common/json.c
index e13bea6a3..e17b07a02 100644
--- a/common/json.c
+++ b/common/json.c
@@ -13,6 +13,7 @@
#include <common/json.h>
#include <common/json_stream.h>
#include <common/node_id.h>
+#include <common/overflows.h>
#include <common/utils.h>
#include <common/wireaddr.h>
#include <errno.h>
@@ -107,6 +108,47 @@ bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num)
return true;
}
+bool json_to_millionths(const char *buffer, const jsmntok_t *tok, u64 *millionths)
+{
+ int decimal_places = -1;
+ bool has_digits = 0;
+
+ *millionths = 0;
+ for (size_t i = tok->start; i < tok->end; i++) {
+ if (isdigit(buffer[i])) {
+ has_digits = true;
+ /* Ignore too much precision */
+ if (decimal_places >= 0 && ++decimal_places > 6)
+ continue;
+ if (mul_overflows_u64(*millionths, 10))
+ return false;
+ *millionths *= 10;
+ if (add_overflows_u64(*millionths, buffer[i] - '0'))
+ return false;
+ *millionths += buffer[i] - '0';
+ } else if (buffer[i] == '.') {
+ if (decimal_places != -1)
+ return false;
+ decimal_places = 0;
+ } else
+ return false;
+ }
+
+ if (!has_digits)
+ return false;
+
+ if (decimal_places == -1)
+ decimal_places = 0;
+
+ while (decimal_places < 6) {
+ if (mul_overflows_u64(*millionths, 10))
+ return false;
+ *millionths *= 10;
+ decimal_places++;
+ }
+ return true;
+}
+
bool json_to_number(const char *buffer, const jsmntok_t *tok,
unsigned int *num)
{
diff --git a/common/test/run-json.c b/common/test/run-json.c
index ed24dfd96..78c33f022 100644
--- a/common/test/run-json.c
+++ b/common/test/run-json.c
@@ -46,6 +46,63 @@ static int test_json_tok_bitcoin_amount(void)
return 0;
}
+static void do_json_tok_millionths(const char *val, bool ok,
+ uint64_t expected)
+{
+ uint64_t amount;
+ jsmntok_t tok;
+
+ tok.start = 0;
+ tok.end = strlen(val);
+
+ assert(json_to_millionths(val, &tok, &amount) == ok);
+ if (ok)
+ assert(amount == expected);
+}
+
+static int test_json_tok_millionths(void)
+{
+ do_json_tok_millionths("", false, 0);
+ do_json_tok_millionths("0..0", false, 0);
+ do_json_tok_millionths("0.0.", false, 0);
+ do_json_tok_millionths(".", false, 0);
+ do_json_tok_millionths("..", false, 0);
+
+ do_json_tok_millionths("0", true, 0);
+ do_json_tok_millionths(".0", true, 0);
+ do_json_tok_millionths("0.", true, 0);
+ do_json_tok_millionths("100", true, 100 * 1000000);
+ do_json_tok_millionths("100.0", true, 100 * 1000000);
+ do_json_tok_millionths("100.", true, 100 * 1000000);
+ do_json_tok_millionths("100.000001", true, 100 * 1000000 + 1);
+ do_json_tok_millionths("100.0000001", true, 100 * 1000000);
+ do_json_tok_millionths(".000009", true, 9);
+ do_json_tok_millionths(".0000099", true, 9);
+ do_json_tok_millionths("18446744073709.551615", true,
+ 18446744073709551615ULL);
+ do_json_tok_millionths("18446744073709.551616", false, 0);
+ do_json_tok_millionths("18446744073709.551625", false, 0);
+ do_json_tok_millionths("18446744073709.551715", false, 0);
+ do_json_tok_millionths("18446744073709.552615", false, 0);
+ do_json_tok_millionths("18446744073709.561615", false, 0);
+ do_json_tok_millionths("18446744073709.651615", false, 0);
+ do_json_tok_millionths("18446744073710.551615", false, 0);
+ do_json_tok_millionths("18446744073809.551615", false, 0);
+ do_json_tok_millionths("18446744074709.551615", false, 0);
+ do_json_tok_millionths("18446744083709.551615", false, 0);
+ do_json_tok_millionths("18446744173709.551615", false, 0);
+ do_json_tok_millionths("18446745073709.551615", false, 0);
+ do_json_tok_millionths("18446754073709.551615", false, 0);
+ do_json_tok_millionths("18446844073709.551615", false, 0);
+ do_json_tok_millionths("18447744073709.551615", false, 0);
+ do_json_tok_millionths("18456744073709.551615", false, 0);
+ do_json_tok_millionths("18546744073709.551615", false, 0);
+ do_json_tok_millionths("19446744073709.551615", false, 0);
+ do_json_tok_millionths("28446744073709.551615", false, 0);
+
+ return 0;
+}
+
static void test_json_tok_size(void)
{
const jsmntok_t *toks;
@@ -190,6 +247,7 @@ int main(void)
test_json_tok_size();
test_json_tok_bitcoin_amount();
+ test_json_tok_millionths();
test_json_delve();
assert(!taken_any());
take_cleanup();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON supports "E" syntax, e.g. 1E2 == 100: https://www.json.org/img/number.png
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading only the JSON syntax, it would even accept 10E2 == 1000.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the above to this PR. I like that it specifically parses floating point numbers that will fit in u64 after being multiplied by 1 million. I don't like that it adds more code and now this PR is +201/-130
lines, whereas it was +103/-123
before.
It would accept both .123
and 456.
neither of which is a valid JSON number.
It would not accept 1e2
which is a valid JSON number and was accepted before by strtod(3)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON supports "E" syntax, e.g. 1E2 == 100: https://www.json.org/img/number.png
Yes, I deliberately excluded that. It's not really a useful for these cases, and I prefer to be explicit. I'll add patches to stop the leading or trailing dot cases, too.
Replace `json_to_double()` (which uses `strtod(3)`) with our own floating-point parsing function `json_to_millionths()` that specifically expects to receive such a number that can fit in a 64 bit integer after being multiplied by 1 million. The main piece of the code in this patch comes from ElementsProject#3535 (comment) Changelog-None
cc73b12
to
f0cc728
Compare
Replace `json_to_double()` (which uses `strtod(3)`) with our own floating-point parsing function `json_to_millionths()` that specifically expects to receive such a number that can fit in a 64 bit integer after being multiplied by 1 million. The main piece of the code in this patch comes from ElementsProject#3535 (comment) Changelog-None
f0cc728
to
80b9da7
Compare
Replace `json_to_double()` (which uses `strtod(3)`) with our own floating-point parsing function `json_to_millionths()` that specifically expects to receive such a number that can fit in a 64 bit integer after being multiplied by 1 million. The main piece of the code in this patch comes from ElementsProject#3535 (comment) Changelog-None
80b9da7
to
2bbaab8
Compare
Replace `json_to_double()` (which uses `strtod(3)`) with our own floating-point parsing function `json_to_millionths()` that specifically expects to receive such a number that can fit in a 64 bit integer after being multiplied by 1 million. The main piece of the code in this patch comes from ElementsProject#3535 (comment) Changelog-None
2bbaab8
to
bec8828
Compare
Changelog-None
Before this patch we used to send `double`s over the wire by just copying them. This is not portable because the internal represenation of a `double` is implementation specific. Instead of this, multiply any floating-point numbers that come from the outside (e.g. JSONs) by 1 million and round them to integers when handling them. * Introduce a new param_millionths() that expects a floating-point number and returns it multipled by 1000000 as an integer. * Replace param_double() and param_percent() with param_millionths() * Previously the riskfactor would be allowed to be negative, which must have been unintentional. This patch changes that to require a non-negative number. Changelog-None
Replace `json_to_double()` (which uses `strtod(3)`) with our own floating-point parsing function `json_to_millionths()` that specifically expects to receive such a number that can fit in a 64 bit integer after being multiplied by 1 million. The main piece of the code in this patch comes from ElementsProject#3535 (comment) Changelog-None
bec8828
to
25c2858
Compare
Ack 25c2858 |
Should it ever be needed to pass |
Before this patch we used to send
double
s over the wire by justcopying them. This is not portable because the internal represenation
of a
double
is implementation specific.Instead of this, multiply any floating-point numbers that come from
the outside (e.g. JSONs) by 1 million and round them to integers when
handling them.
Introduce a new param_millionths() that expects a floating-point
number and returns it multipled by 1000000 as an integer.
Replace param_double() and param_percent() with param_millionths()
Previously the riskfactor would be allowed to be negative, which must
have been unintentional. This patch changes that to require a
non-negative number.