Skip to content

Commit 8d15229

Browse files
VagabondNopey
andcommitted
Try to improve the fixedpt_str function and add unit tests for it (#1054)
* Try to improve the fixedpt_str function and add unit tests for it * Fix overflow in fixedpt_fracpart macro * Futher clarify the function * Clarify fixed point string printing test suite name * Clearer handling of negative numbers * Apply suggestions from code review --------- Co-authored-by: Magnus Larsen <[email protected]>
1 parent 43834e3 commit 8d15229

File tree

5 files changed

+245
-40
lines changed

5 files changed

+245
-40
lines changed

src/game/objects/har.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ void har_walk_to(object *obj, int destination) {
308308

309309
fixedpt vx = h->fwd_speedf * object_get_direction(obj);
310310
char vx_buf[FIXEDPT_STR_BUFSIZE];
311-
fixedpt_str(vx, vx_buf, -1);
311+
fixedpt_str(vx, vx_buf, sizeof(vx_buf), -1);
312312
log_debug("set velocity to %s", vx_buf);
313313
object_set_vel(obj, vec2f_createf(vx, 0));
314314

@@ -512,7 +512,7 @@ void har_move(object *obj) {
512512
return;
513513
} else if(h->walk_destination > 0) {
514514
char posx_buf[FIXEDPT_STR_BUFSIZE];
515-
fixedpt_str(obj->pos.fx, posx_buf, -1);
515+
fixedpt_str(obj->pos.fx, posx_buf, sizeof(posx_buf), -1);
516516
log_debug("still walking to %d, at %s", h->walk_destination, posx_buf);
517517
}
518518

@@ -1235,8 +1235,8 @@ int har_collide_with_har(object *obj_a, object *obj_b, int loop) {
12351235
if(obj_b->gs->match_settings.rehit && b->state == STATE_FALLEN &&
12361236
(!object_is_airborne(obj_b) || b->endurance <= 0)) {
12371237
char xbuf[FIXEDPT_STR_BUFSIZE], ybuf[FIXEDPT_STR_BUFSIZE];
1238-
fixedpt_str(obj_b->pos.fx, xbuf, -1);
1239-
fixedpt_str(obj_b->pos.fy, ybuf, -1);
1238+
fixedpt_str(obj_b->pos.fx, xbuf, sizeof(xbuf), -1);
1239+
fixedpt_str(obj_b->pos.fy, ybuf, sizeof(ybuf), -1);
12401240
log_debug("REHIT is not possible %d %s %s %d", object_is_airborne(obj_b), xbuf, ybuf, b->endurance);
12411241
return 0;
12421242
}
@@ -1281,15 +1281,15 @@ int har_collide_with_har(object *obj_a, object *obj_b, int loop) {
12811281
// TODO use 90% of the block pushback as cornerpush for now
12821282
fixedpt push = -1 * object_get_direction(obj_a) *
12831283
((move->block_stun - 2) * fixedpt_rconst(0.74 * 0.9) + fixedpt_rconst(0.9));
1284-
fixedpt_str(push, pushbuf, -1);
1284+
fixedpt_str(push, pushbuf, sizeof(pushbuf), -1);
12851285
log_debug("doing block cornerpush of %s", pushbuf);
12861286
vel.fx += push;
12871287
object_set_vel(obj_a, vel);
12881288
} else {
12891289
vec2f vel = object_get_vel(obj_b);
12901290
fixedpt push = -1 * object_get_direction(obj_b) *
12911291
((move->block_stun - 2) * fixedpt_rconst(0.74) + fixedpt_rconst(1));
1292-
fixedpt_str(push, pushbuf, -1);
1292+
fixedpt_str(push, pushbuf, sizeof(pushbuf), -1);
12931293
log_debug("doing block pushback of %s", pushbuf);
12941294
vel.fx += push;
12951295
object_set_vel(obj_b, vel);
@@ -1451,8 +1451,8 @@ void har_collide_with_projectile(object *o_har, object *o_pjt) {
14511451
if(o_har->gs->match_settings.rehit && h->state == STATE_FALLEN &&
14521452
(!object_is_airborne(o_har) || h->endurance <= 0)) {
14531453
char xbuf[FIXEDPT_STR_BUFSIZE], ybuf[FIXEDPT_STR_BUFSIZE];
1454-
fixedpt_str(o_har->pos.fx, xbuf, -1);
1455-
fixedpt_str(o_har->pos.fy, ybuf, -1);
1454+
fixedpt_str(o_har->pos.fx, xbuf, sizeof(xbuf), -1);
1455+
fixedpt_str(o_har->pos.fy, ybuf, sizeof(ybuf), -1);
14561456
log_debug("REHIT is not possible %d %s %s %d", object_is_airborne(o_har), xbuf, ybuf, h->endurance);
14571457
return;
14581458
}

src/game/protos/object.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -579,8 +579,8 @@ void object_set_animation(object *obj, animation *ani) {
579579
// Debug texts
580580
if(obj->cur_animation->id == -1) {
581581
char bufx[FIXEDPT_STR_BUFSIZE], bufy[FIXEDPT_STR_BUFSIZE];
582-
fixedpt_str(obj->pos.fx, bufx, -1);
583-
fixedpt_str(obj->pos.fy, bufy, -1);
582+
fixedpt_str(obj->pos.fx, bufx, sizeof(bufx), -1);
583+
fixedpt_str(obj->pos.fy, bufy, sizeof(bufy), -1);
584584
log_debug("Custom object set to (x,y) = (%s,%s).", bufx, bufy);
585585
} else {
586586
/*log_debug("Animation object %d set to (x,y) = (%f,%f) with \"%s\".", */

src/utils/fixedptc.h

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ typedef uint32_t fixedptud;
8484
#define FIXEDPT_MIN INT16_MIN
8585
#define FIXEDPT_MAX INT16_MAX
8686
#define FIXEDPTU_MAX UINT16_MAX
87+
#define FIXEDPT_WCHARS 5 // 16 bit numbers can use at most 5 decimal digits
8788
#elif FIXEDPT_BITS == 32
8889
typedef int32_t fixedpt;
8990
typedef int64_t fixedptd;
@@ -92,14 +93,16 @@ typedef uint64_t fixedptud;
9293
#define FIXEDPT_MIN INT32_MIN
9394
#define FIXEDPT_MAX INT32_MAX
9495
#define FIXEDPTU_MAX UINT32_MAX
96+
#define FIXEDPT_WCHARS 10 // 32 bit numbers can use at most 10 decimal digits
9597
#elif FIXEDPT_BITS == 64
9698
typedef int64_t fixedpt;
9799
typedef __int128_t fixedptd;
98100
typedef uint64_t fixedptu;
99101
typedef __uint128_t fixedptud;
100102
#define FIXEDPT_MIN INT64_MIN
101103
#define FIXEDPT_MAX INT64_MAX
102-
#define FIXEDPTU_MAX UINT64_MAX
104+
#define FIXEDPTU_MAX UINT64_MAX // 64 bit numbers can use at most 20 decimal digits
105+
#define FIXEDPT_WCHARS 20
103106
#else
104107
#error "FIXEDPT_BITS must be equal to 16, 32, or 64"
105108
#endif
@@ -125,7 +128,7 @@ typedef __uint128_t fixedptud;
125128
#define fixedpt_xmul(A, B) ((fixedpt)(((fixedptd)(A) * (fixedptd)(B)) / FIXEDPT_ONE))
126129
#define fixedpt_xmul3(A, B, C) fixedpt_xmul(fixedpt_xmul((A), (B)), (C))
127130
#define fixedpt_xdiv(A, B) ((fixedpt)(((fixedptd)(A) * FIXEDPT_ONE) / (fixedptd)(B)))
128-
#define fixedpt_fracpart(A) ((fixedpt)(A) & FIXEDPT_FMASK)
131+
#define fixedpt_fracpart(A) ((fixedptud)(A) & FIXEDPT_FMASK)
129132

130133
#define FIXEDPT_ONE ((fixedpt)((fixedpt)1 << FIXEDPT_FBITS))
131134
#define FIXEDPT_ONE_HALF (FIXEDPT_ONE >> 1)
@@ -194,66 +197,110 @@ static inline fixedpt fixedpt_div(fixedpt A, fixedpt B) {
194197
* of decimal digits will be used (2 for 32-bit fixedpt width, 10 for
195198
* 64-bit fixedpt width); If set to -2, "all" of the digits will
196199
* be returned, meaning there will be invalid, bogus digits outside the
197-
* specified precisions.
200+
* specified precisions. If set to -3, all the zero digits will be trimmed
201+
* from the end of the decimal digits.
202+
*
203+
* Returns the number of bytes written to `str`, not counting the null terminator.
198204
*/
199-
static inline void fixedpt_str(fixedpt A, char *str, int max_dec) {
205+
static inline size_t fixedpt_str(fixedpt A, char *str, size_t bufsize, int max_dec) {
200206
// boy this function sure is safe with pointers :(
201-
int ndec = 0, slen = 0;
202-
char tmp[12] = {0};
207+
size_t nint = 0; // number of integer digits
208+
size_t nfrac = 0; // number of fractional digits
209+
size_t slen = 0; // string length used
210+
211+
// accumulator for integer digits, can hold the max number of digits for FIXEDPT_BITS
212+
char acc[FIXEDPT_WCHARS] = {0};
203213
fixedptud fr, ip;
214+
fixedptud magnitude = (fixedptud)(fixedptd)A;
204215
const fixedptud one = (fixedptud)1 << FIXEDPT_BITS;
205216
const fixedptud mask = one - 1;
217+
size_t num_places = 0;
206218

207-
if(max_dec == -1)
219+
// return if the destination is null or too small
220+
if(str == NULL || bufsize <= 0) {
221+
return 0;
222+
} else if(bufsize < 2) {
223+
str[0] = '\0';
224+
return 0;
225+
}
226+
227+
if(max_dec == -1) {
208228
#if FIXEDPT_BITS == 16 || FIXEDPT_BITS == 32
209229
#if FIXEDPT_FBITS < 16
210-
max_dec = 2;
230+
num_places = 2;
211231
#else
212-
max_dec = 4;
232+
num_places = 4;
213233
#endif
214234
#elif FIXEDPT_BITS == 64
215-
max_dec = 10;
235+
num_places = 10;
216236
#else
217237
#error Invalid width
218238
#endif
219-
else if(max_dec == -2)
220-
max_dec = 15;
239+
} else if(max_dec == -2) {
240+
num_places = 15;
241+
} else if(max_dec == -3) {
242+
num_places = 15;
243+
} else if(max_dec < 0) {
244+
// other negative values of max_dec not allowed
245+
return 0;
246+
} else {
247+
num_places = max_dec;
248+
}
221249

222250
if(A < 0) {
223251
str[slen++] = '-';
224-
A *= -1;
252+
magnitude *= -1;
225253
}
226254

227-
ip = fixedpt_toint(A);
255+
// Accumulate the integer digits in reverse order
256+
// by dividing the integer component by 10 each time.
257+
// Use do/while so at least a 0 is written
258+
ip = fixedpt_toint(magnitude);
228259
do {
229-
tmp[ndec++] = '0' + ip % 10;
260+
acc[nint++] = '0' + ip % 10;
230261
ip /= 10;
231-
} while(ip != 0);
262+
} while(ip != 0 && nint < sizeof(acc) - 1);
263+
264+
// reverse the accumulated digits into the output string
265+
while(nint > 0 && slen < bufsize - 1) {
266+
str[slen++] = acc[--nint];
267+
}
232268

233-
while(ndec > 0)
234-
str[slen++] = tmp[--ndec];
235-
str[slen++] = '.';
269+
// check if we want, and have space for, decimal digits
270+
if(num_places > 0 && slen < bufsize - 1) {
271+
str[slen++] = '.';
236272

237-
fr = (fixedpt_fracpart(A) << FIXEDPT_WBITS) & mask;
238-
do {
239-
fr = (fr & mask) * 10;
273+
fr = (fixedpt_fracpart(magnitude) << FIXEDPT_WBITS) & mask;
274+
while(nfrac < num_places && slen < bufsize - 1) {
275+
fr = (fr & mask) * 10;
240276

241-
str[slen++] = '0' + (fr >> FIXEDPT_BITS) % 10;
242-
ndec++;
243-
} while(fr != 0 && ndec < max_dec);
277+
str[slen++] = '0' + (fr >> FIXEDPT_BITS) % 10;
278+
nfrac++;
279+
}
244280

245-
if(ndec > 1 && str[slen - 1] == '0')
246-
str[slen - 1] = '\0'; /* cut off trailing 0 */
247-
else
248-
str[slen] = '\0';
281+
// trim any trailing 0s, if desired
282+
while(max_dec == -3 && nfrac > 0 && str[slen - 1] == '0') {
283+
slen--;
284+
nfrac--;
285+
}
286+
287+
// if we didn't add any decimal values, omit the decimal place
288+
if(nfrac == 0) {
289+
slen--;
290+
}
291+
}
292+
293+
size_t bytes_used = slen < bufsize ? slen : bufsize - 1;
294+
str[bytes_used] = '\0';
295+
return bytes_used;
249296
}
250297

251298
/* Converts the given fixedpt number into a string, using a static
252299
* (non-threadsafe) string buffer */
253300
static inline char *fixedpt_cstr(const fixedpt A, const int max_dec) {
254301
static char str[25];
255302

256-
fixedpt_str(A, str, max_dec);
303+
fixedpt_str(A, str, sizeof(str), max_dec);
257304
return (str);
258305
}
259306

testing/test_fixedptc_str.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#include "utils/fixedptc.h"
2+
#include <CUnit/CUnit.h>
3+
4+
void test_zero_value(void) {
5+
char buf[FIXEDPT_STR_BUFSIZE];
6+
fixedpt zero = 0;
7+
size_t written;
8+
9+
// Default precision (-1)
10+
written = fixedpt_str(zero, buf, sizeof(buf), -1);
11+
CU_ASSERT_STRING_EQUAL(buf, "0.00");
12+
CU_ASSERT_EQUAL(written, 4);
13+
14+
// Trim all decimals (-3)
15+
written = fixedpt_str(zero, buf, sizeof(buf), -3);
16+
CU_ASSERT_STRING_EQUAL(buf, "0");
17+
CU_ASSERT_EQUAL(written, 1);
18+
}
19+
20+
void test_positive_integers(void) {
21+
char buf[FIXEDPT_STR_BUFSIZE];
22+
fixedpt val = 123 << FIXEDPT_FBITS; // 123.0
23+
size_t written;
24+
25+
// With 2 decimal places
26+
written = fixedpt_str(val, buf, sizeof(buf), 2);
27+
CU_ASSERT_STRING_EQUAL(buf, "123.00");
28+
CU_ASSERT_EQUAL(written, 6);
29+
30+
// Trim trailing zeros
31+
written = fixedpt_str(val, buf, sizeof(buf), -3);
32+
CU_ASSERT_STRING_EQUAL(buf, "123");
33+
CU_ASSERT_EQUAL(written, 3);
34+
}
35+
36+
void test_negative_numbers(void) {
37+
char buf[FIXEDPT_STR_BUFSIZE];
38+
fixedpt val = -(456 << FIXEDPT_FBITS); // -456.0
39+
size_t written;
40+
41+
written = fixedpt_str(val, buf, sizeof(buf), 2);
42+
CU_ASSERT_STRING_EQUAL(buf, "-456.00");
43+
CU_ASSERT_EQUAL(written, 7);
44+
}
45+
46+
void test_exact_fractions(void) {
47+
char buf[FIXEDPT_STR_BUFSIZE];
48+
fixedpt val = FIXEDPT_ONE + FIXEDPT_ONE_HALF; // 1.5
49+
size_t written;
50+
51+
// Normal precision
52+
written = fixedpt_str(val, buf, sizeof(buf), 1);
53+
CU_ASSERT_STRING_EQUAL(buf, "1.5");
54+
CU_ASSERT_EQUAL(written, 3);
55+
56+
// Trim zeros
57+
written = fixedpt_str(val, buf, sizeof(buf), -3);
58+
CU_ASSERT_STRING_EQUAL(buf, "1.5");
59+
CU_ASSERT_EQUAL(written, 3);
60+
}
61+
62+
void test_buffer_limits(void) {
63+
char small_buf[5];
64+
fixedpt val = (123 << FIXEDPT_FBITS) + FIXEDPT_ONE_HALF; // 123.5
65+
size_t written;
66+
67+
// Try to write to tiny buffer
68+
written = fixedpt_str(val, small_buf, sizeof(small_buf), 2);
69+
CU_ASSERT_STRING_EQUAL(small_buf, "123");
70+
CU_ASSERT_EQUAL(written, 3);
71+
}
72+
73+
void test_zero_trimming(void) {
74+
char buf[FIXEDPT_STR_BUFSIZE];
75+
fixedpt val = (123 << FIXEDPT_FBITS) + (FIXEDPT_ONE / 4); // 123.25
76+
size_t written;
77+
78+
// Add extra zeros
79+
written = fixedpt_str(val, buf, sizeof(buf), 4);
80+
CU_ASSERT_STRING_EQUAL(buf, "123.2500");
81+
CU_ASSERT_EQUAL(written, 8);
82+
83+
// Trim to significant digits
84+
written = fixedpt_str(val, buf, sizeof(buf), -3);
85+
CU_ASSERT_STRING_EQUAL(buf, "123.25");
86+
CU_ASSERT_EQUAL(written, 6);
87+
}
88+
89+
void test_max_precision(void) {
90+
char buf[FIXEDPT_STR_BUFSIZE];
91+
fixedpt val = FIXEDPT_ONE / 10; // 0.1
92+
size_t written;
93+
94+
// Request maximum precision
95+
written = fixedpt_str(val, buf, sizeof(buf), -2);
96+
CU_ASSERT_STRING_EQUAL(buf, "0.097656250000000");
97+
CU_ASSERT_EQUAL(written, 17);
98+
}
99+
100+
void test_min_value(void) {
101+
char buf[FIXEDPT_STR_BUFSIZE];
102+
fixedpt val = FIXEDPT_MIN; // INT32_MIN >> 8
103+
size_t written;
104+
105+
written = fixedpt_str(val, buf, sizeof(buf), 2);
106+
CU_ASSERT_STRING_EQUAL(buf, "-8388608.00");
107+
CU_ASSERT_EQUAL(written, 11);
108+
}
109+
110+
void test_near_min_value(void) {
111+
char buf[FIXEDPT_STR_BUFSIZE];
112+
fixedpt val = FIXEDPT_MIN + (FIXEDPT_ONE / 4);
113+
size_t written;
114+
115+
written = fixedpt_str(val, buf, sizeof(buf), 2);
116+
CU_ASSERT_STRING_EQUAL(buf, "-8388607.75");
117+
CU_ASSERT_EQUAL(written, 11);
118+
}
119+
120+
void test_max_value(void) {
121+
char buf[FIXEDPT_STR_BUFSIZE];
122+
fixedpt val = FIXEDPT_MAX; // INT32_MAX >> 8
123+
size_t written;
124+
125+
written = fixedpt_str(val, buf, sizeof(buf), 2);
126+
CU_ASSERT_STRING_EQUAL(buf, "8388607.99");
127+
CU_ASSERT_EQUAL(written, 10);
128+
}
129+
130+
void test_near_max_value(void) {
131+
char buf[FIXEDPT_STR_BUFSIZE];
132+
fixedpt val = FIXEDPT_MAX - (FIXEDPT_ONE / 4);
133+
size_t written;
134+
135+
written = fixedpt_str(val, buf, sizeof(buf), 2);
136+
CU_ASSERT_STRING_EQUAL(buf, "8388607.74");
137+
CU_ASSERT_EQUAL(written, 10);
138+
}
139+
140+
void fixedpt_str_test_suite(CU_pSuite suite) {
141+
CU_add_test(suite, "Zero Handling", test_zero_value);
142+
CU_add_test(suite, "Positive Integers", test_positive_integers);
143+
CU_add_test(suite, "Negative Numbers", test_negative_numbers);
144+
CU_add_test(suite, "Exact Fractions", test_exact_fractions);
145+
CU_add_test(suite, "Buffer Limits", test_buffer_limits);
146+
CU_add_test(suite, "Zero Trimming", test_zero_trimming);
147+
CU_add_test(suite, "Max Precision", test_max_precision);
148+
CU_add_test(suite, "Min Value", test_min_value);
149+
CU_add_test(suite, "Near Min Value", test_near_min_value);
150+
CU_add_test(suite, "Max Value", test_max_value);
151+
CU_add_test(suite, "Near Max Value", test_near_max_value);
152+
}

0 commit comments

Comments
 (0)