Branch data Line data Source code
1 : : // Copyright 2011-2025 David Robillard <d@drobilla.net>
2 : : // SPDX-License-Identifier: ISC
3 : :
4 : : #undef NDEBUG
5 : :
6 : : #include "double_test_utils.h"
7 : : #include "int_test_utils.h"
8 : : #include "num_test_utils.h"
9 : :
10 : : #include <exess/exess.h>
11 : :
12 : : #include <assert.h>
13 : : #include <float.h>
14 : : #include <math.h>
15 : : #include <stdint.h>
16 : : #include <stdio.h>
17 : : #include <string.h>
18 : :
19 : : static void
20 : 35 : check_read(const char* const string,
21 : : const ExessStatus expected_status,
22 : : const double expected_value,
23 : : const size_t expected_count)
24 : : {
25 : 35 : double value = (double)NAN;
26 : 35 : const ExessResult r = exess_read_double(string, &value);
27 : :
28 [ - + ]: 35 : assert(r.status == expected_status);
29 [ - + ]: 35 : assert(r.count == expected_count);
30 [ - + ]: 35 : assert(double_matches(value, expected_value));
31 : 35 : }
32 : :
33 : : static void
34 : 1 : test_read_double(void)
35 : : {
36 : : // Limits
37 : 1 : check_read("-1.7976931348623157E308", EXESS_SUCCESS, -DBL_MAX, 23);
38 : 1 : check_read("-2.2250738585072014E-308", EXESS_SUCCESS, -DBL_MIN, 24);
39 : 1 : check_read("2.2250738585072014E-308", EXESS_SUCCESS, DBL_MIN, 23);
40 : 1 : check_read("1.7976931348623157E308", EXESS_SUCCESS, DBL_MAX, 22);
41 : :
42 : : // Beyond limits
43 : 1 : check_read("1e-326", EXESS_SUCCESS, 0.0, 6);
44 : 1 : check_read("12345678901234567123", EXESS_LOSS, 12345678901234567000.0, 20);
45 : 1 : check_read("1e309", EXESS_SUCCESS, (double)INFINITY, 5);
46 : 1 : check_read("1e310", EXESS_SUCCESS, (double)INFINITY, 5);
47 : :
48 : : // Non-canonical form
49 : 1 : check_read("+1E3", EXESS_SUCCESS, 1e3, 4);
50 : 1 : check_read("1E+3", EXESS_SUCCESS, 1e3, 4);
51 : 1 : check_read("+1.5E3", EXESS_SUCCESS, 1.5e3, 6);
52 : 1 : check_read(".5E3", EXESS_SUCCESS, 0.5e3, 4);
53 : 1 : check_read("+.5E3", EXESS_SUCCESS, 0.5e3, 5);
54 : 1 : check_read("-.5E3", EXESS_SUCCESS, -0.5e3, 5);
55 : 1 : check_read("1.E3", EXESS_SUCCESS, 1e3, 4);
56 : 1 : check_read("+1.E3", EXESS_SUCCESS, 1e3, 5);
57 : 1 : check_read("-1.E3", EXESS_SUCCESS, -1e3, 5);
58 : 1 : check_read("-100.25", EXESS_SUCCESS, -100.25, 7);
59 : 1 : check_read("+00.10", EXESS_SUCCESS, 0.10, 6);
60 : :
61 : : // Special values
62 : 1 : check_read("NaN", EXESS_SUCCESS, (double)NAN, 3);
63 : 1 : check_read("-INF", EXESS_SUCCESS, (double)-INFINITY, 4);
64 : 1 : check_read("-0.0E0", EXESS_SUCCESS, -0.0, 6);
65 : 1 : check_read("0.0E0", EXESS_SUCCESS, 0.0, 5);
66 : 1 : check_read("+0.0E0", EXESS_SUCCESS, 0.0, 6);
67 : 1 : check_read("INF", EXESS_SUCCESS, (double)INFINITY, 3);
68 : 1 : check_read("+INF", EXESS_SUCCESS, (double)INFINITY, 4);
69 : :
70 : : // No exponent
71 : 1 : check_read("1", EXESS_SUCCESS, 1.0, 1);
72 : 1 : check_read("2.3", EXESS_SUCCESS, 2.3, 3);
73 : 1 : check_read("-4.5", EXESS_SUCCESS, -4.5, 4);
74 : :
75 : : // Leading whitespace
76 : 1 : check_read("\t\n\r 1.2", EXESS_SUCCESS, 1.2, 7);
77 : :
78 : : // Garbage
79 : 1 : check_read("true", EXESS_EXPECTED_DIGIT, (double)NAN, 0);
80 : 1 : check_read("+true", EXESS_EXPECTED_DIGIT, (double)NAN, 1);
81 : 1 : check_read("-false", EXESS_EXPECTED_DIGIT, (double)NAN, 1);
82 : 1 : check_read("1.0eX", EXESS_EXPECTED_DIGIT, (double)NAN, 4);
83 : 1 : check_read("1.0EX", EXESS_EXPECTED_DIGIT, (double)NAN, 4);
84 : 1 : }
85 : :
86 : : /// Check that `str` is a canonical xsd:double string
87 : : static void
88 : 9 : check_canonical(const char* const str)
89 : : {
90 [ + + + + : 9 : if (!strcmp(str, "NaN") || !strcmp(str, "-INF") || !strcmp(str, "INF")) {
+ + ]
91 : 3 : return;
92 : : }
93 : :
94 [ - + ]: 6 : assert(strlen(str) > 4); // Shortest possible is something like 1.2E3
95 [ + + - + ]: 6 : assert(str[0] == '-' || is_digit(str[0]));
96 : :
97 : 6 : const int first_digit = str[0] == '-' ? 1 : 0;
98 [ - + ]: 6 : assert(is_digit(str[first_digit]));
99 [ - + ]: 6 : assert(str[first_digit + 1] == '.');
100 [ - + ]: 6 : assert(is_digit(str[first_digit + 2]));
101 : :
102 : 6 : const char* const e = strchr(str, 'E');
103 [ - + ]: 6 : assert(e);
104 [ - + ]: 6 : assert(*e == 'E');
105 [ + + - + ]: 6 : assert(*(e + 1) == '-' || is_digit(*(e + 1)));
106 : : }
107 : :
108 : : static void
109 : 9 : check_write(const double value,
110 : : const size_t buf_size,
111 : : const char* const expected_string)
112 : : {
113 : 9 : char buf[EXESS_MAX_DOUBLE_LENGTH + 1] = {
114 : : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, //
115 : : 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, //
116 : : 21, 22, 23};
117 : :
118 [ - + ]: 9 : assert(buf_size <= sizeof(buf));
119 : :
120 : 9 : const ExessResult r = exess_write_double(value, buf_size, buf);
121 [ - + ]: 9 : assert(!r.status);
122 [ - + ]: 9 : assert(r.count == strlen(buf));
123 [ - + ]: 9 : assert(buf[0]);
124 [ - + ]: 9 : assert(expected_string);
125 [ - + ]: 9 : assert(!strcmp(buf, expected_string));
126 : 9 : check_canonical(buf);
127 : :
128 [ - + ]: 9 : assert(exess_write_double(value, 0, NULL).count == r.count);
129 : :
130 [ + + ]: 100 : for (size_t space = 0; space < buf_size; ++space) {
131 : 91 : buf[0] = 1;
132 : :
133 : 91 : const ExessResult s = exess_write_double(value, space, buf);
134 [ - + ]: 91 : assert(s.status == EXESS_NO_SPACE);
135 [ - + ]: 91 : assert(s.count == r.count);
136 [ - + ]: 91 : assert(buf[0] == (space ? 0 : 1));
137 : : }
138 : 9 : }
139 : :
140 : : static void
141 : 1 : test_write_double(void)
142 : : {
143 : : // Numbers
144 : 1 : check_write(-100.25, 10, "-1.0025E2");
145 : 1 : check_write(-0.0, 7, "-0.0E0");
146 : 1 : check_write(0.0, 6, "0.0E0");
147 : 1 : check_write(10.25, 8, "1.025E1");
148 : :
149 : : // Limits
150 : 1 : check_write(DBL_MIN, 24, "2.2250738585072014E-308");
151 : 1 : check_write(DBL_MAX, 23, "1.7976931348623157E308");
152 : :
153 : : // Special values
154 : 1 : check_write((double)NAN, 4, "NaN");
155 : 1 : check_write((double)-INFINITY, 5, "-INF");
156 : 1 : check_write((double)INFINITY, 4, "INF");
157 : 1 : }
158 : :
159 : : static void
160 : 14361 : check_round_trip(const double value)
161 : : {
162 : 14361 : double parsed_value = 0.0;
163 : :
164 : 14361 : char buf[EXESS_MAX_DOUBLE_LENGTH + 1] = {
165 : : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, //
166 : : 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, //
167 : : 21, 22, 23};
168 : :
169 [ - + ]: 14361 : assert(!exess_write_double(value, sizeof(buf), buf).status);
170 [ - + ]: 14361 : assert(!exess_read_double(buf, &parsed_value).status);
171 [ - + ]: 14361 : assert(double_matches(parsed_value, value));
172 : 14361 : }
173 : :
174 : : static void
175 : 1 : test_round_trip(const ExessNumTestOptions opts)
176 : : {
177 : 1 : check_round_trip((double)NAN);
178 : 1 : check_round_trip(-(double)INFINITY);
179 : 1 : check_round_trip(DBL_MIN);
180 : 1 : check_round_trip(-0.0);
181 : 1 : check_round_trip(0.0);
182 : 1 : check_round_trip(DBL_MAX);
183 : 1 : check_round_trip((double)INFINITY);
184 : :
185 : 1 : check_round_trip(5.0);
186 : 1 : check_round_trip(50.0);
187 : 1 : check_round_trip(500000000000000000000.0);
188 : 1 : check_round_trip(-0.5);
189 : 1 : check_round_trip(0.5);
190 : 1 : check_round_trip(0.05);
191 : 1 : check_round_trip(0.005);
192 : 1 : check_round_trip(0.00000000000000000005);
193 : :
194 : : // Normal limits
195 : 1 : check_round_trip(nextafter(DBL_MIN, (double)INFINITY));
196 : 1 : check_round_trip(nextafter(DBL_EPSILON, (double)INFINITY));
197 : 1 : check_round_trip(nextafter(DBL_MAX, -(double)INFINITY));
198 : :
199 : : // Subnormals
200 : 1 : check_round_trip(nextafter(0.0, 1.0));
201 : 1 : check_round_trip(nextafter(nextafter(0.0, 1.0), 2.0));
202 : 1 : check_round_trip(nextafter(0.0, -1.0));
203 : 1 : check_round_trip(nextafter(nextafter(0.0, -1.0), -2.0));
204 : :
205 : : // Various tricky cases
206 : 1 : check_round_trip(1e23);
207 : 1 : check_round_trip(6.02951420360127e-309);
208 : 1 : check_round_trip(9.17857104364115e+288);
209 : 1 : check_round_trip(2.68248422823759e+22);
210 : :
211 : : // Powers of two (where the lower boundary is closer)
212 [ + + ]: 2048 : for (int i = -1023; i <= 1023; ++i) {
213 : 2047 : check_round_trip(pow(2, i));
214 : : }
215 : :
216 : 1 : fprintf(stderr, "Testing xsd:double randomly with seed %u\n", opts.seed);
217 : :
218 : 1 : uint64_t state = opts.seed;
219 [ + + ]: 4097 : for (uint64_t i = 0; i < opts.n_tests; ++i) {
220 : 4096 : state = lcg64(state);
221 : :
222 : 4096 : const double value = random_double(state);
223 : :
224 : 4096 : check_round_trip(nextafter(value, -(double)INFINITY));
225 : 4096 : check_round_trip(value);
226 : 4096 : check_round_trip(nextafter(value, (double)INFINITY));
227 : :
228 : 4096 : print_num_test_progress(i, opts.n_tests);
229 : : }
230 : 1 : }
231 : :
232 : : int
233 : 1 : main(int argc, char** argv)
234 : : {
235 : : const ExessNumTestOptions opts =
236 : 1 : parse_num_test_options(argc, argv, 4096U, 0, UINT32_MAX);
237 : :
238 [ + - ]: 1 : if (!opts.error) {
239 : 1 : test_read_double();
240 : 1 : test_write_double();
241 : 1 : test_round_trip(opts);
242 : : }
243 : :
244 : 1 : return (int)opts.error;
245 : : }
|