diff options
Diffstat (limited to 'serve')
| -rw-r--r-- | serve/message-json.c | 30 | ||||
| -rw-r--r-- | serve/test-message-json.c | 68 |
2 files changed, 98 insertions, 0 deletions
diff --git a/serve/message-json.c b/serve/message-json.c index 8c288ce..643cc28 100644 --- a/serve/message-json.c +++ b/serve/message-json.c @@ -46,6 +46,36 @@ static size_t json_number(const unsigned char *p, size_t size, bool *integer) { static bool json_double(const unsigned char *text, size_t size, double *value) { bool integer; if (!size || json_number(text, size, &integer) != size || size > 40) return false; + /* Digits m < 2^53 and a power of ten 10^e, |e| <= 22, are exact, so one + * multiplication or division rounds m * 10^e correctly (Clinger's fast + * path); other numbers go to strtod. */ + static const double tens[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + const unsigned char *p = text, *end = text + size; + bool negative = *p == '-', exact = true; + uint64_t m = 0; + int exponent = 0; + p += negative; + for (; p < end && *p >= '0' && *p <= '9'; ++p) { + exact = exact && m < UINT64_C(900719925474099); + m = m * 10 + (uint64_t)(*p - '0'); + } + if (p < end && *p == '.') + for (++p; p < end && *p >= '0' && *p <= '9'; ++p, --exponent) { + exact = exact && m < UINT64_C(900719925474099); + m = m * 10 + (uint64_t)(*p - '0'); + } + if (p < end) { /* an exponent */ + bool below = *++p == '-'; + int e = 0; + for (p += *p == '-' || *p == '+'; p < end; ++p) e = e < 1000 ? e * 10 + (*p - '0') : e; + exponent += below ? -e : e; + } + if (exact && exponent >= -22 && exponent <= 22) { + double v = exponent < 0 ? (double)m / tens[-exponent] : (double)m * tens[exponent]; + *value = negative ? -v : v; + return true; + } char digits[48]; memcpy(digits, text, size); digits[size] = 0; diff --git a/serve/test-message-json.c b/serve/test-message-json.c new file mode 100644 index 0000000..618a363 --- /dev/null +++ b/serve/test-message-json.c @@ -0,0 +1,68 @@ +/* JSON numbers are read as strtod reads them, bit for bit: in the forms a + * client writes (a program's shortest digits, fixed decimals, exponents), at + * the edges of the exact path (2^53, 10^22) and past them, and refused when + * not finite or not JSON. */ +#include "message-json.h" +#include <math.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define CHECK(x) do { if (!(x)) { fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, #x); exit(1); } } while (0) + +static uint64_t next_random(uint64_t *state) { + uint64_t x = *state; + x ^= x << 13, x ^= x >> 7, x ^= x << 17; + return *state = x; +} + +/* The text read as a number field: true with its value, or false. */ +static bool read_number(const char *text, double *value) { + static const PureField number = {.type = PURE_FIELD_NUMBER}; + unsigned char prefix[8]; + if (!pure_json_fixed(&number, (const unsigned char *)text, strlen(text), prefix)) return false; + memcpy(value, prefix, sizeof(*value)); + return true; +} + +static void same_as_strtod(const char *text) { + double got, want = strtod(text, NULL); + CHECK(read_number(text, &got) && !memcmp(&got, &want, sizeof(got))); +} + +int main(void) { + static const char *const edges[] = { + "0", "-0", "0.0", "-0.0", "1", "0.1", "0.2", "0.30000000000000004", "123456.789", "1e22", "1e23", "1E-22", + "1e-23", "9007199254740991", "9007199254740992", "9007199254740993", "900719925474099.3", "12345678901234567890", + "0.000000000000000000000001", "1.7976931348623157e308", "4.9406564584124654e-324", "2.2250738585072014e-308", + "-123.456e-7", "5e+0", "0.5e1", "1234567890.0987654321"}; + for (size_t i = 0; i < sizeof(edges) / sizeof(*edges); ++i) same_as_strtod(edges[i]); + static const char *const refused[] = {"1e309", "-1e400", "01", "1.", ".5", "+1", "1e", "--1", "0x10", "1.5e+", "NaN"}; + for (size_t i = 0; i < sizeof(refused) / sizeof(*refused); ++i) { + double value; + CHECK(!read_number(refused[i], &value)); + } + uint64_t seed = 0x6a736f6eu; + size_t read = 0; + for (unsigned i = 0; i < 400000; ++i) { + uint64_t bits = next_random(&seed); + double value; + switch (i % 4) { + case 0: value = (double)(int64_t)(bits % 2000001 - 1000000) / 100.0; break; + case 1: value = (double)(bits % 100000000) / pow(10, (double)(bits >> 50 & 15)); break; + case 2: memcpy(&value, &bits, sizeof(value)); if (!isfinite(value)) value = -2.5; break; + default: value = ldexp((double)(bits >> 11), (int)(bits % 120) - 90); break; + } + char text[64]; + snprintf(text, sizeof(text), "%.17g", value); + same_as_strtod(text); + snprintf(text, sizeof(text), "%.*f", (int)(bits >> 56) % 12, value); + if (strlen(text) < 40) same_as_strtod(text); + snprintf(text, sizeof(text), "%.*e", (int)(bits >> 58) % 17, value); + same_as_strtod(text); + read += 3; + } + printf("{\"json_numbers\":%zu}\n", read); + return 0; +} |
