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 "float_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 : 58 : check_read(const char* const string,
21 : : const ExessStatus expected_status,
22 : : const float expected_value,
23 : : const size_t expected_count)
24 : : {
25 : 58 : float value = NAN;
26 : 58 : const ExessResult r = exess_read_float(string, &value);
27 : :
28 [ - + ]: 58 : assert(r.status == expected_status);
29 [ - + ]: 58 : assert(r.count == expected_count);
30 [ - + ]: 58 : assert(float_matches(value, expected_value));
31 : 58 : }
32 : :
33 : : static void
34 : 2 : test_read_float(void)
35 : : {
36 : : // Limits
37 : 2 : check_read("-3.40282347E38", EXESS_SUCCESS, -FLT_MAX, 14);
38 : 2 : check_read("-1.17549435E-38", EXESS_SUCCESS, -FLT_MIN, 15);
39 : 2 : check_read("1.17549435E-38", EXESS_SUCCESS, FLT_MIN, 14);
40 : 2 : check_read("3.40282347E38", EXESS_SUCCESS, FLT_MAX, 13);
41 : :
42 : : // Special values
43 : 2 : check_read("NaN", EXESS_SUCCESS, NAN, 3);
44 : 2 : check_read("-INF", EXESS_SUCCESS, -INFINITY, 4);
45 : 2 : check_read("-0.0E0", EXESS_SUCCESS, -0.0f, 6);
46 : 2 : check_read("0.0E0", EXESS_SUCCESS, 0.0f, 5);
47 : 2 : check_read("+0.0E0", EXESS_SUCCESS, 0.0f, 6);
48 : 2 : check_read("INF", EXESS_SUCCESS, INFINITY, 3);
49 : 2 : check_read("+INF", EXESS_SUCCESS, INFINITY, 4);
50 : :
51 : : // Various normal cases
52 : 2 : check_read("-1.0E0", EXESS_SUCCESS, -1.0f, 6);
53 : 2 : check_read("1.0E0", EXESS_SUCCESS, +1.0f, 5);
54 : 2 : check_read("5.0E0", EXESS_SUCCESS, 5.0f, 5);
55 : 2 : check_read("5.0E1", EXESS_SUCCESS, 50.0f, 5);
56 : 2 : check_read("5.0E9", EXESS_SUCCESS, 5000000000.0f, 5);
57 : 2 : check_read("-5.0E-1", EXESS_SUCCESS, -0.5f, 7);
58 : 2 : check_read("5.0E-1", EXESS_SUCCESS, 0.5f, 6);
59 : 2 : check_read("6.25E-2", EXESS_SUCCESS, 0.0625f, 7);
60 : 2 : check_read("7.8125E-3", EXESS_SUCCESS, 0.0078125f, 9);
61 : :
62 : : // No exponent
63 : 2 : check_read("1", EXESS_SUCCESS, 1.0f, 1);
64 : 2 : check_read("2.3", EXESS_SUCCESS, 2.3f, 3);
65 : 2 : check_read("-4.5", EXESS_SUCCESS, -4.5f, 4);
66 : :
67 : : // Trailing garbage
68 : 2 : check_read("1.2.", EXESS_SUCCESS, 1.2f, 3);
69 : :
70 : : // Garbage
71 : 2 : check_read("true", EXESS_EXPECTED_DIGIT, NAN, 0);
72 : 2 : check_read("+true", EXESS_EXPECTED_DIGIT, NAN, 1);
73 : 2 : check_read("-false", EXESS_EXPECTED_DIGIT, NAN, 1);
74 : 2 : check_read("1.0eX", EXESS_EXPECTED_DIGIT, NAN, 4);
75 : 2 : check_read("1.0EX", EXESS_EXPECTED_DIGIT, NAN, 4);
76 : 2 : }
77 : :
78 : : static void
79 : 2 : test_float_string_length(void)
80 : : {
81 : : // Limits
82 [ - + ]: 2 : assert(exess_write_float(FLT_MIN, 0, NULL).count == 14);
83 [ - + ]: 2 : assert(exess_write_float(FLT_MAX, 0, NULL).count == 13);
84 : :
85 : : // Special values
86 [ - + ]: 2 : assert(exess_write_float((float)NAN, 0, NULL).count == 3);
87 [ - + ]: 2 : assert(exess_write_float(-1.0f, 0, NULL).count == 6);
88 [ - + ]: 2 : assert(exess_write_float(-0.0f, 0, NULL).count == 6);
89 [ - + ]: 2 : assert(exess_write_float(0.0f, 0, NULL).count == 5);
90 [ - + ]: 2 : assert(exess_write_float(1.0f, 0, NULL).count == 5);
91 [ - + ]: 2 : assert(exess_write_float((float)INFINITY, 0, NULL).count == 3);
92 [ - + ]: 2 : assert(exess_write_float((float)-INFINITY, 0, NULL).count == 4);
93 : 2 : }
94 : :
95 : : /// Check that `str` is a canonical xsd:float string
96 : : static void
97 : 18 : check_canonical(const char* const str)
98 : : {
99 [ + + + + : 18 : if (!strcmp(str, "NaN") || !strcmp(str, "-INF") || !strcmp(str, "INF")) {
+ + ]
100 : 6 : return;
101 : : }
102 : :
103 [ - + ]: 12 : assert(strlen(str) > 4); // Shortest possible is something like 1.2E3
104 [ + + - + ]: 12 : assert(str[0] == '-' || is_digit(str[0]));
105 : :
106 : 12 : const int first_digit = str[0] == '-' ? 1 : 0;
107 [ - + ]: 12 : assert(is_digit(str[first_digit]));
108 [ - + ]: 12 : assert(str[first_digit + 1] == '.');
109 [ - + ]: 12 : assert(is_digit(str[first_digit + 2]));
110 : :
111 : 12 : const char* const e = strchr(str, 'E');
112 [ - + ]: 12 : assert(e);
113 [ - + ]: 12 : assert(*e == 'E');
114 [ + + - + ]: 12 : assert(*(e + 1) == '-' || is_digit(*(e + 1)));
115 : : }
116 : :
117 : : static void
118 : 18 : check_write(const float value,
119 : : const size_t buf_size,
120 : : const char* const expected_string)
121 : : {
122 : 18 : char buf[EXESS_MAX_FLOAT_LENGTH + 1] = {
123 : : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
124 : :
125 [ - + ]: 18 : assert(buf_size <= sizeof(buf));
126 : :
127 : 18 : const ExessResult r = exess_write_float(value, buf_size, buf);
128 [ - + ]: 18 : assert(!r.status);
129 [ - + ]: 18 : assert(r.count == strlen(buf));
130 [ - + ]: 18 : assert(buf[0]);
131 [ - + ]: 18 : assert(expected_string);
132 [ - + ]: 18 : assert(!strcmp(buf, expected_string));
133 : 18 : check_canonical(buf);
134 : :
135 [ - + ]: 18 : assert(exess_write_float(value, 0, NULL).count == r.count);
136 : :
137 [ + + ]: 164 : for (size_t space = 0; space < buf_size; ++space) {
138 : 146 : buf[0] = 1;
139 : :
140 : 146 : const ExessResult s = exess_write_float(value, space, buf);
141 [ - + ]: 146 : assert(s.status == EXESS_NO_SPACE);
142 [ - + ]: 146 : assert(s.count == r.count);
143 [ - + ]: 146 : assert(buf[0] == (space ? 0 : 1));
144 : : }
145 : 18 : }
146 : :
147 : : static void
148 : 2 : test_write_float(void)
149 : : {
150 : : // Numbers
151 : 2 : check_write(-100.25f, 10, "-1.0025E2");
152 : 2 : check_write(-0.0f, 7, "-0.0E0");
153 : 2 : check_write(0.0f, 6, "0.0E0");
154 : 2 : check_write(10.25f, 8, "1.025E1");
155 : :
156 : : // Limits
157 : 2 : check_write(FLT_MIN, 15, "1.17549435E-38");
158 : 2 : check_write(FLT_MAX, 14, "3.40282346E38");
159 : :
160 : : // Special values
161 : 2 : check_write(NAN, 4, "NaN");
162 : 2 : check_write(-INFINITY, 5, "-INF");
163 : 2 : check_write(INFINITY, 4, "INF");
164 : 2 : }
165 : :
166 : : static void
167 : 12811 : check_round_trip(const float value)
168 : : {
169 : 12811 : char buf[EXESS_MAX_FLOAT_LENGTH + 1] = {
170 : : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
171 : :
172 : 12811 : float parsed_value = 0.0f;
173 [ - + ]: 12811 : assert(!exess_write_float(value, sizeof(buf), buf).status);
174 [ - + ]: 12811 : assert(!exess_read_float(buf, &parsed_value).status);
175 [ - + ]: 12811 : assert(float_matches(parsed_value, value));
176 : 12811 : }
177 : :
178 : : static void
179 : 2 : test_round_trip(const ExessNumTestOptions opts)
180 : : {
181 : 2 : check_round_trip(NAN);
182 : 2 : check_round_trip(FLT_MIN);
183 : 2 : check_round_trip(-0.0f);
184 : 2 : check_round_trip(0.0f);
185 : 2 : check_round_trip(FLT_MAX);
186 : :
187 [ + + ]: 2 : if (opts.exhaustive) {
188 : 1 : fprintf(stderr, "Testing xsd:float exhaustively\n");
189 : :
190 [ + + ]: 514 : for (int64_t i = opts.low; i <= opts.high; ++i) {
191 : 513 : const float value = float_from_rep((uint32_t)i);
192 : :
193 : 513 : check_round_trip(value);
194 : 513 : print_num_test_progress((uint64_t)(i - opts.low), (uint64_t)opts.high);
195 : : }
196 : : } else {
197 : 1 : fprintf(stderr, "Testing xsd:float randomly with seed %u\n", opts.seed);
198 : :
199 : 1 : uint32_t rep = opts.seed;
200 [ + + ]: 4097 : for (uint64_t i = 0; i < opts.n_tests; ++i) {
201 : 4096 : rep = lcg32(rep);
202 : :
203 : 4096 : const float value = random_float(rep);
204 : :
205 : 4096 : check_round_trip(nextafterf(value, -INFINITY));
206 : 4096 : check_round_trip(value);
207 : 4096 : check_round_trip(nextafterf(value, INFINITY));
208 : :
209 : 4096 : print_num_test_progress(i, opts.n_tests);
210 : : }
211 : : }
212 : 2 : }
213 : :
214 : : int
215 : 2 : main(int argc, char** argv)
216 : : {
217 : : const ExessNumTestOptions opts =
218 : 2 : parse_num_test_options(argc, argv, 4096U, 0U, UINT32_MAX);
219 : :
220 [ + - ]: 2 : if (!opts.error) {
221 : 2 : test_read_float();
222 : 2 : test_float_string_length();
223 : 2 : test_write_float();
224 : 2 : test_round_trip(opts);
225 : : }
226 : :
227 : 2 : return (int)opts.error;
228 : : }
|