Host C Specs


Directory: src/c/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 96.4% 54 / 0 / 56
Functions: 100.0% 8 / 0 / 8
Branches: 85.2% 46 / 0 / 54

core/text/number_format.c
Line Branch Exec Source
1 /**
2 * @file number_format.c
3 * @brief Pure number and string formatting, no SDK behind it.
4 *
5 * @ingroup lib_core
6 */
7 #include "text/number_format.h"
8 #include "text/cstring_fit.h"
9
10 #include <stdio.h>
11 #include <string.h>
12
13 /**
14 * @brief The size of a number without its sign, as an unsigned value.
15 *
16 * abs() on INT_MIN has no answer that fits an int, and the stock price comes straight off the wire.
17 * Doing the negation in unsigned maths gives the right size for every int, INT_MIN included.
18 *
19 * @param value The number to take the size of.
20 * @return The value with its sign dropped.
21 */
22 15 static unsigned int magnitude_of(int value)
23 {
24
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
15 return value < 0 ? 0u - (unsigned int)value : (unsigned int)value;
25 }
26
27 7 void number_group(char *buffer, size_t size, int value)
28 {
29
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (size == 0)
30 {
31 ✗ return;
32 }
33
34 // strip the sign into an unsigned magnitude so INT_MIN stays well-defined
35 // then walk the digits inserting separators
36 char digits[16];
37 7 snprintf(digits, sizeof(digits), "%u", magnitude_of(value));
38
39 7 size_t len = strlen(digits);
40 char out[24];
41 7 size_t w = 0;
42
43
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
7 if (value < 0 && w < sizeof(out) - 1)
44 {
45 2 out[w++] = '-';
46 }
47
48
3/4
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
45 for (size_t i = 0; i < len && w < sizeof(out) - 1; i++)
49 {
50 // a separator precedes every group of three counted from the right
51
4/4
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 22 times.
38 if (i > 0 && (len - i) % 3 == 0)
52 {
53 9 out[w++] = '\'';
54 }
55
56
1/2
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
38 if (w < sizeof(out) - 1)
57 {
58 38 out[w++] = digits[i];
59 }
60 }
61
62 7 out[w] = '\0';
63
64 7 cstring_fit(buffer, out, size);
65 }
66
67 4 void fmt_int_or_dash(char *buffer, size_t size, int value, const char *fmt)
68 {
69
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (value < 0)
70 {
71 2 snprintf(buffer, size, "--");
72 }
73 else
74 {
75 2 snprintf(buffer, size, fmt, value);
76 }
77 4 }
78
79 4 void fmt_hundredths(char *buffer, size_t size, int value)
80 {
81 // pull the sign out front so a value between -1 and 0 still shows the minus, since the whole
82 // part would be 0 and would otherwise drop it
83
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 const char *sign = value < 0 ? "-" : "";
84 4 unsigned int magnitude = magnitude_of(value);
85
86 4 snprintf(buffer, size, "%s%u.%02u", sign, magnitude / 100, magnitude % 100);
87 4 }
88
89 4 void fmt_pct_signed(char *buffer, size_t size, int value)
90 {
91 // same reason as above, plus a gain wears its plus so the direction reads without the colour
92
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
4 const char *sign = value > 0 ? "+" : (value < 0 ? "-" : "");
93 4 unsigned int magnitude = magnitude_of(value);
94
95 4 snprintf(buffer, size, "%s%u.%02u%%", sign, magnitude / 100, magnitude % 100);
96 4 }
97
98 40 void copy_bounded(char *dst, uint8_t cap, const uint8_t *src, uint8_t len)
99 {
100
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 39 times.
40 if (cap == 0)
101 {
102 // nowhere to even put the terminator. checked up front because cap - 1 below would wrap
103 // to 255 through the int promotion and copy a quarter kilobyte over whatever is there
104 1 return;
105 }
106
107 39 uint8_t copy = len;
108
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 35 times.
39 if (copy > cap - 1)
109 {
110 4 copy = cap - 1;
111 }
112
2/2
✓ Branch 0 taken 130 times.
✓ Branch 1 taken 39 times.
169 for (uint8_t c = 0; c < copy; c++)
113 {
114 130 dst[c] = (char)src[c];
115 }
116 39 dst[copy] = '\0';
117 }
118
119 10 bool cstring_is_clean(const char *s, uint16_t size)
120 {
121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!s)
122 {
123 ✗ return false;
124 }
125
126
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 1 time.
45 for (uint16_t i = 0; i < size; i++)
127 {
128 // plain char is signed on the host and unsigned on the watch, so read it as unsigned
129 // to make the range test mean the same thing in a spec and on the wrist
130 44 unsigned char c = (unsigned char)s[i];
131
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 38 times.
44 if (c == '\0')
132 {
133 6 return i > 0; // ends inside its room, and said something on the way
134 }
135
136 // control bytes are what a damaged blob looks like. everything from 0x20 up is kept,
137 // including UTF-8 lead and continuation bytes, so a place name like Zurich spelled with
138 // an umlaut keeps its bytes across a reboot
139
4/4
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 35 times.
38 if (c < 0x20 || c == 0x7F)
140 {
141 3 return false;
142 }
143 }
144
145 1 return false; // ran out of room without ever ending
146 }
147
148 3 bool cstring_setting_is_clean(const char *s, uint16_t size, const char *default_str)
149 {
150
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
3 bool empty_is_a_value = !default_str || default_str[0] == '\0';
151
6/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
3 if (empty_is_a_value && s && size > 0 && s[0] == '\0')
152 {
153 1 return true;
154 }
155
156 2 return cstring_is_clean(s, size);
157 }
158