|
1
|
/* |
|
2
|
** Test MSVC C89 compatible implementations of the following C99 functions: |
|
3
|
** - double rint( double x ) |
|
4
|
** Rounds a floating-point value to the nearest integer in floating-point |
|
5
|
** format. |
|
6
|
** - int snprintf( char *buffer, size_t count, const char *format, ... ) |
|
7
|
** Writes formatted data to a string. |
|
8
|
** |
|
9
|
** NOTE: These implementations aim to provide the main functionality, not |
|
10
|
** the exact behavior as specified in C99 standard. |
|
11
|
** |
|
12
|
** BUILD: cl test-msc98-rint-snprintf.c |
|
13
|
** gcc test-msc98-rint-snprintf.c -lm ## for reference vs. non-MSVC |
|
14
|
*/ |
|
15
|
|
|
16
|
#include <stdio.h> |
|
17
|
#include <stdlib.h> |
|
18
|
#include <string.h> |
|
19
|
#include <math.h> |
|
20
|
#include <limits.h> |
|
21
|
|
|
22
|
#define TEST_MSC89 1 |
|
23
|
|
|
24
|
#if defined(_MSC_VER) |
|
25
|
#if (defined(TEST_MSC89) || (_MSC_VER < 1900)) /* before MSVC 2015 */ |
|
26
|
#include <stdarg.h> |
|
27
|
|
|
28
|
/* NOTE: On truncation, this version of snprintf returns the input count, not |
|
29
|
** the expected number of chars to fully output the requested format (as |
|
30
|
** done in the C99 standard implementation). However the truncation test should |
|
31
|
** still be applicable (nret >= count). |
|
32
|
*/ |
|
33
|
static __forceinline |
|
34
|
int c89_snprintf(char *buf, size_t count, const char *fmt, ...){ |
|
35
|
va_list argptr; |
|
36
|
int n; |
|
37
|
if( count==0 ) return 0; |
|
38
|
va_start(argptr, fmt); |
|
39
|
n = _vsprintf_p(buf, count, fmt, argptr); |
|
40
|
va_end(argptr); |
|
41
|
|
|
42
|
/* force zero-termination to avoid some known MSVC bugs */ |
|
43
|
if( count>0 ){ |
|
44
|
buf[count - 1] = '\0'; |
|
45
|
if( n<0 ) n = count; |
|
46
|
} |
|
47
|
return n; |
|
48
|
} |
|
49
|
|
|
50
|
#if defined(_WIN64) |
|
51
|
#include <emmintrin.h> |
|
52
|
#include <limits.h> |
|
53
|
|
|
54
|
static __forceinline |
|
55
|
double c89_rint(double v){ |
|
56
|
return ( v<0.0 && v>=-0.5 ? -0.0 |
|
57
|
: ( v!=0 && v>LLONG_MIN && v<LLONG_MAX |
|
58
|
? _mm_cvtsd_si64(_mm_load_sd(&v)) : v ) ); /* SSE2 */ |
|
59
|
} |
|
60
|
#else |
|
61
|
static __forceinline |
|
62
|
double c89_rint(double v){ |
|
63
|
double rn; |
|
64
|
__asm |
|
65
|
{ |
|
66
|
FLD v |
|
67
|
FRNDINT |
|
68
|
FSTP rn |
|
69
|
FWAIT |
|
70
|
}; |
|
71
|
return rn; |
|
72
|
} |
|
73
|
#endif /* _WIN64 */ |
|
74
|
#endif /* (defined(TEST_MSC89) || (_MSC_VER < 1900)) */ |
|
75
|
|
|
76
|
#if (_MSC_VER < 1900) /* before MSVC 2015 */ |
|
77
|
# define snprintf c89_snprintf |
|
78
|
# define rint c89_rint |
|
79
|
#else |
|
80
|
# define HAVE_C99_RINT 1 |
|
81
|
#endif |
|
82
|
|
|
83
|
#elif !defined(_MSC_VER) |
|
84
|
# define HAVE_C99_RINT 1 |
|
85
|
# define c89_snprintf snprintf |
|
86
|
# define c89_rint rint |
|
87
|
#endif /* defined(_MSC_VER) */ |
|
88
|
|
|
89
|
|
|
90
|
#include <assert.h> |
|
91
|
|
|
92
|
#define SNPRINTF c89_snprintf |
|
93
|
#define RINT c89_rint |
|
94
|
|
|
95
|
int test_rint() |
|
96
|
{ |
|
97
|
const char *TESTNAME = "rint"; |
|
98
|
const struct test_data { |
|
99
|
double v, expected; |
|
100
|
} data[] = { /* round to the nearest or even integer */ |
|
101
|
#ifdef HAVE_C99_RINT |
|
102
|
{INFINITY,INFINITY}, |
|
103
|
#endif |
|
104
|
{(double)(LLONG_MAX/10000LL) + 0.7,(double)(LLONG_MAX/10000LL) + 1.}, |
|
105
|
{5.5,6.},{5.4,5.},{5.2,5.},{5.,5.}, |
|
106
|
{4.9,5.},{4.5,4.},{4.4,4.},{4.0,4.}, |
|
107
|
{3.7,4.},{3.5,4.},{3.2,3.},{3.0,3.}, |
|
108
|
{2.7,3.},{2.5,2.},{2.2,2.},{2.0,2.}, |
|
109
|
{1.6,2.},{1.5,2.0},{1.3,1.0},{1.0,1.0}, |
|
110
|
{0.9,1.},{0.8,1.},{0.5,0.},{0.49999999999999994,0.}, |
|
111
|
{0.4,0.},{0.1,0.},{0.,0.} |
|
112
|
}; |
|
113
|
const size_t ndata = sizeof(data)/sizeof(data[0]); |
|
114
|
int nfailed = 0; |
|
115
|
int start = 0, end = ndata; |
|
116
|
int i; |
|
117
|
int done = 0; |
|
118
|
|
|
119
|
/* do two passes over the test data (positives, negatives) */ |
|
120
|
do { |
|
121
|
double sign = ( start<end ? 1. : -1. ); |
|
122
|
for(i=start; ( start< end ? i<end : i>end ); ( start< end ? ++i : --i )){ |
|
123
|
int passed = 0; |
|
124
|
double v = sign*data[i].v; |
|
125
|
double rn = c89_rint(v); |
|
126
|
double expected = sign*data[i].expected; |
|
127
|
#ifdef HAVE_C99_RINT |
|
128
|
{ |
|
129
|
double rint_expected = rint(v); |
|
130
|
int matched = ( expected==rint_expected ); |
|
131
|
if( !matched ){ |
|
132
|
fprintf(stderr, "E:%s|Expected test data[%d]={%.17lf,%.1lf} does not match the actual rint() value=%.1lf\n",__FUNCTION__, |
|
133
|
i, v, expected, rint_expected); |
|
134
|
} |
|
135
|
assert(matched); |
|
136
|
expected = rint_expected; |
|
137
|
} |
|
138
|
#endif |
|
139
|
passed = ( rn==expected ); |
|
140
|
fprintf(( passed ? stdout : stderr ), |
|
141
|
"T:%s|c89_rint(%.17lf)=%.1lf expected=%.1lf\t[%s]\n", TESTNAME, |
|
142
|
v, rn, expected, |
|
143
|
( passed ? "PASS" : "FAIL" )); |
|
144
|
if( !passed ) ++nfailed; |
|
145
|
} |
|
146
|
|
|
147
|
if( start<end ){ |
|
148
|
int swap = start; |
|
149
|
start = end - 1; end = swap - 1; |
|
150
|
}else{ |
|
151
|
done = 1; |
|
152
|
} |
|
153
|
}while( !done ); |
|
154
|
|
|
155
|
if( nfailed ){ |
|
156
|
fprintf(stderr,"T:%s|FAILED %d test\n\n", TESTNAME, nfailed); |
|
157
|
}else{ |
|
158
|
printf("T:%s|PASSED\n\n", TESTNAME); |
|
159
|
} |
|
160
|
|
|
161
|
return nfailed; |
|
162
|
} |
|
163
|
|
|
164
|
int test_snprintf() |
|
165
|
{ |
|
166
|
const char *TESTNAME = "snprintf"; |
|
167
|
int nfailed = 0; |
|
168
|
#define TEST_BUF_MAXSIZE 256 |
|
169
|
const struct test_data { |
|
170
|
size_t bufsize; |
|
171
|
const char *fmt, *expected, *full; |
|
172
|
} data[] = { |
|
173
|
{TEST_BUF_MAXSIZE,"snprintf(buf, %d)","snprintf(buf, 17)","snprintf(buf, 17)"}, |
|
174
|
{17,"snprintf(buf, %d)","snprintf(buf, 17","snprintf(buf, 17)"}, |
|
175
|
{2,"snprintf(buf, %d)","s","snprintf(buf, 17)"}, |
|
176
|
{0,"snprintf(buf, %d)","","snprintf(buf, 17)"}, |
|
177
|
}; |
|
178
|
const size_t ndata = sizeof(data)/sizeof(data[0]); |
|
179
|
char buf[TEST_BUF_MAXSIZE] = {0}; |
|
180
|
int i; |
|
181
|
|
|
182
|
for(i=0; i<ndata; ++i){ |
|
183
|
int passed = 0; |
|
184
|
size_t count = data[i].bufsize; |
|
185
|
const char *fmt = data[i].fmt; |
|
186
|
const char *full = data[i].full; |
|
187
|
const char *expected = data[i].expected; |
|
188
|
const int truncate_expected = ( count<=strlen(full) ); |
|
189
|
const int expected_nret = ( !truncate_expected |
|
190
|
? strlen(expected) : count ); |
|
191
|
const int expected_zero_at = ( !truncate_expected |
|
192
|
? expected_nret : expected_nret - 1 ); |
|
193
|
int nret; |
|
194
|
buf[( count>0 ? count - 1 : 0 )] = '\0'; |
|
195
|
nret = c89_snprintf(buf, count, fmt, strlen(fmt)); |
|
196
|
#ifdef HAVE_C99_RINT |
|
197
|
{ |
|
198
|
char snprintf_expected[TEST_BUF_MAXSIZE] = {0}; |
|
199
|
int snprintf_expected_nret = snprintf(snprintf_expected, count, fmt, strlen(fmt)); |
|
200
|
int matched = ( strcmp(expected, snprintf_expected)==0 ); |
|
201
|
int matched_nret = ( expected_nret==snprintf_expected_nret ); |
|
202
|
if( !matched ){ |
|
203
|
fprintf(stderr, "E:%s|Expected test data[%d]={'%s','%s'} does not match the actual snprintf() value='%s'\n",__FUNCTION__, |
|
204
|
i, buf, expected, snprintf_expected); |
|
205
|
} |
|
206
|
|
|
207
|
/* NOTE: This implementation of c89_snprintf() on truncation returns |
|
208
|
** the input count, not the expected number of chars needed to fully |
|
209
|
** output the requested format. So warn, only ifthe expected nret is |
|
210
|
** less than the count. |
|
211
|
*/ |
|
212
|
if( !matched_nret && expected_nret<count ){ |
|
213
|
fprintf(stderr, "W:%s|Expected return value=%d for test data[%d]={'%s','%s'} does not match the actual snprintf() return value=%d\n",__FUNCTION__, |
|
214
|
expected_nret, i, buf, expected, snprintf_expected_nret); |
|
215
|
} |
|
216
|
assert(matched); |
|
217
|
expected = snprintf_expected; |
|
218
|
} |
|
219
|
#endif |
|
220
|
passed = ( nret==expected_nret ); |
|
221
|
fprintf(( passed ? stdout : stderr ), |
|
222
|
"T:%s|c89_snprintf(%lu,\"%s\", %d) nret=%d expected=%d\t[%s]\n", TESTNAME, |
|
223
|
(unsigned long)count, fmt, (int)strlen(fmt), nret, expected_nret, |
|
224
|
( passed ? "PASS" : "FAIL" )); |
|
225
|
if( !passed ) ++nfailed; |
|
226
|
|
|
227
|
if( count ){ |
|
228
|
passed = ( buf[expected_zero_at]=='\0' ); |
|
229
|
fprintf(( passed ? stdout : stderr ), |
|
230
|
"T:%s|c89_snprintf(%lu,\"%s\", %d) s[%d]=%d expected=%d\t[%s]\n", TESTNAME, |
|
231
|
(unsigned long)count, fmt, (int)strlen(fmt), expected_zero_at, buf[expected_zero_at],'\0', |
|
232
|
( passed ? "PASS" : "FAIL" )); |
|
233
|
if( !passed ) ++nfailed; |
|
234
|
} |
|
235
|
|
|
236
|
passed = ( strcmp(buf, expected)==0 ); |
|
237
|
fprintf((passed ? stdout : stderr), |
|
238
|
"T:%s|c89_snprintf(%lu,\"%s\", %d)=\"%s\" expected=\"%s\"\t[%s]\n", TESTNAME, |
|
239
|
(unsigned long)count, fmt, (int)strlen(fmt), buf, expected, |
|
240
|
( passed ? "PASS" : "FAIL" )); |
|
241
|
if( !passed ) ++nfailed; |
|
242
|
} |
|
243
|
|
|
244
|
if( nfailed ){ |
|
245
|
fprintf(stderr,"T:%s|FAILED %d tests\n\n", TESTNAME, nfailed); |
|
246
|
}else{ |
|
247
|
printf("T:%s|PASSED\n\n", TESTNAME); |
|
248
|
} |
|
249
|
return nfailed; |
|
250
|
} |
|
251
|
|
|
252
|
int main(int argc, char *argv[]) |
|
253
|
{ |
|
254
|
int iarg; |
|
255
|
struct testrun { |
|
256
|
char rint, snprintf; |
|
257
|
int status; |
|
258
|
} testrun = {0, 0, 0}; |
|
259
|
|
|
260
|
printf("Usage: %s [TEST]...\n", argv[0]); |
|
261
|
printf("Test MSVC C89 compatible implementations of selected C99 functions.\n" |
|
262
|
"Run the selected TEST, by default 'all'; optionally exclude tests from the run.\n"); |
|
263
|
printf("Example: %s all -snprintf\n", argv[0]); |
|
264
|
printf("\nTests:\n" |
|
265
|
" all, rint, snprintf\n" |
|
266
|
"\n -TEST\t\texclude the test from the run\n" |
|
267
|
"\n" |
|
268
|
); |
|
269
|
if( argc>1 |
|
270
|
&& ( strcmp(argv[1], "/?")==0 || strcmp(argv[1], "/h")==0 |
|
271
|
|| strcmp(argv[1], "/H")==0 || strcmp(argv[1], "--help")==0 ) ){ |
|
272
|
return 0; |
|
273
|
} |
|
274
|
|
|
275
|
testrun.rint = testrun.snprintf = ( argc==1 ); |
|
276
|
for(iarg=1; iarg<argc; ++iarg){ |
|
277
|
const char *name = argv[iarg]; |
|
278
|
char runit = 1; |
|
279
|
if( argv[iarg][0]=='-' ){ |
|
280
|
runit = 0; |
|
281
|
name = &(argv[iarg][1]); |
|
282
|
} |
|
283
|
|
|
284
|
if( strcmp(name, "all")==0 ){ |
|
285
|
testrun.rint = testrun.snprintf = runit; |
|
286
|
}else if( strcmp(name, "rint")==0 ){ |
|
287
|
testrun.rint = runit; |
|
288
|
}else if( strcmp(name, "snprintf")==0 ){ |
|
289
|
testrun.snprintf = runit; |
|
290
|
}else{ |
|
291
|
testrun.status = 2; |
|
292
|
fprintf(stderr, "\nE|Invalid test requested: '%s'\n", name); |
|
293
|
} |
|
294
|
} |
|
295
|
|
|
296
|
if( testrun.status==2 ){ |
|
297
|
return testrun.status; |
|
298
|
} |
|
299
|
|
|
300
|
#ifndef _MSC_VER |
|
301
|
fprintf(stderr, "W|Non-MSVC mode: testing against the native implementations\n\n"); |
|
302
|
#endif |
|
303
|
|
|
304
|
if( testrun.rint ) |
|
305
|
testrun.status |= test_rint(); |
|
306
|
|
|
307
|
if( testrun.snprintf ) |
|
308
|
testrun.status |= test_snprintf(); |
|
309
|
|
|
310
|
if( testrun.status==0 ) |
|
311
|
printf("\nI|All selected tests completed successfully\n"); |
|
312
|
else |
|
313
|
fprintf(stderr, "\nE|Some of the selected tests failed\n"); |
|
314
|
return testrun.status; |
|
315
|
} |
|
316
|
|
|
317
|
|