source: mainline/uspace/lib/c/generic/capa.c@ a163d10

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a163d10 was de65624, checked in by Jiri Svoboda <jiri@…>, 6 years ago

Fix too many integer digits when rounding capacity up

Apparently, capacity_format test could never have passed.

  • Property mode set to 100644
File size: 6.8 KB
Line 
1/*
2 * Copyright (c) 2015 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libc
30 * @{
31 */
32/**
33 * @file Storage capacity specification.
34 */
35
36#include <capa.h>
37#include <errno.h>
38#include <imath.h>
39#include <stdio.h>
40#include <str.h>
41
42/** Simplified capacity parameters */
43enum {
44 /** Simplified capacity maximum integer digits */
45 scapa_max_idig = 3,
46 /** Simplified capacity maximum significant digits */
47 scapa_max_sdig = 4
48};
49
50static const char *cu_str[] = {
51 [cu_byte] = "B",
52 [cu_kbyte] = "kB",
53 [cu_mbyte] = "MB",
54 [cu_gbyte] = "GB",
55 [cu_tbyte] = "TB",
56 [cu_pbyte] = "PB",
57 [cu_ebyte] = "EB",
58 [cu_zbyte] = "ZB",
59 [cu_ybyte] = "YB"
60};
61
62void capa_from_blocks(uint64_t nblocks, size_t block_size, capa_spec_t *capa)
63{
64 uint64_t tsize;
65
66 tsize = nblocks * block_size;
67 capa->m = tsize;
68 capa->dp = 0;
69 capa->cunit = cu_byte;
70}
71
72/** Convert capacity to blocks.
73 *
74 * If the value of bytes is not integer, it is properly rounded. If the number
75 * of bytes is not divisible by the number of blocks, it is rounded
76 * up to an integer number of blocks.
77 *
78 * A capacity value entails precision, i.e. it corresponds to a range
79 * of values. @a cvsel selects the value to return. @c cv_nom gives
80 * the nominal (middle) value, @c cv_min gives the minimum value
81 * and @c cv_max gives the maximum value.
82 */
83errno_t capa_to_blocks(capa_spec_t *capa, capa_vsel_t cvsel, size_t block_size,
84 uint64_t *rblocks)
85{
86 int exp;
87 uint64_t bytes;
88 uint64_t f;
89 uint64_t adj;
90 uint64_t blocks;
91 uint64_t rem;
92 errno_t rc;
93
94 exp = capa->cunit * 3 - capa->dp;
95 if (exp < 0) {
96 rc = ipow10_u64(-exp, &f);
97 if (rc != EOK)
98 return ERANGE;
99 bytes = (capa->m + (f / 2)) / f;
100 if (bytes * f - (f / 2) != capa->m)
101 return ERANGE;
102 } else {
103 rc = ipow10_u64(exp, &f);
104 if (rc != EOK)
105 return ERANGE;
106
107 adj = 0;
108 switch (cvsel) {
109 case cv_nom:
110 adj = 0;
111 break;
112 case cv_min:
113 adj = -(f / 2);
114 break;
115 case cv_max:
116 adj = f / 2 - 1;
117 break;
118 }
119
120 bytes = capa->m * f + adj;
121 if ((bytes - adj) / f != capa->m)
122 return ERANGE;
123 }
124
125 rem = bytes % block_size;
126 if ((bytes + rem) < bytes)
127 return ERANGE;
128
129 blocks = (bytes + rem) / block_size;
130
131 *rblocks = blocks;
132 return EOK;
133}
134
135/** Simplify and round capacity to a human-friendly form.
136 *
137 * Change unit and round the number so that we have at most three integer
138 * digits and at most two fractional digits, e.g abc.xy <unit>.
139 */
140void capa_simplify(capa_spec_t *capa)
141{
142 uint64_t div;
143 uint64_t maxv;
144 unsigned sdig;
145 unsigned rdig;
146 errno_t rc;
147
148 /* Change units so that we have at most @c scapa_max_idig integer digits */
149 rc = ipow10_u64(scapa_max_idig, &maxv);
150 assert(rc == EOK);
151
152 rc = ipow10_u64(capa->dp, &div);
153 assert(rc == EOK);
154
155 /* Change units until we have no more than scapa_max_idig integer digits */
156 while (capa->m / div >= maxv) {
157 ++capa->cunit;
158 capa->dp += 3;
159 div = div * 1000;
160 }
161
162 /* Round the number so that we have at most @c scapa_max_sdig significant digits */
163 sdig = 1 + ilog10_u64(capa->m); /* number of significant digits */
164 if (sdig > scapa_max_sdig) {
165 /* Number of digits to remove */
166 rdig = sdig - scapa_max_sdig;
167 if (rdig > capa->dp)
168 rdig = capa->dp;
169
170 rc = ipow10_u64(rdig, &div);
171 assert(rc == EOK);
172
173 /* Division with rounding */
174 capa->m = (capa->m + (div / 2)) / div;
175 capa->dp -= rdig;
176 }
177
178 /*
179 * If we rounded up from something like 999.95 to 1000.0,, we still
180 * have more than scapa_max_idig integer digits and need to change
181 * units once more.
182 */
183 rc = ipow10_u64(capa->dp, &div);
184 assert(rc == EOK);
185
186 if (capa->m / div >= 1000) {
187 ++capa->cunit;
188 capa->dp += 3;
189
190 /*
191 * We now have one more significant digit than we want
192 * so round to one less digits
193 */
194 capa->m = (capa->m + 5) / 10;
195 --capa->dp;
196 }
197}
198
199errno_t capa_format(capa_spec_t *capa, char **rstr)
200{
201 errno_t rc;
202 int ret;
203 const char *sunit;
204 uint64_t ipart;
205 uint64_t fpart;
206 uint64_t div;
207
208 sunit = NULL;
209
210 assert(capa->cunit < CU_LIMIT);
211
212 rc = ipow10_u64(capa->dp, &div);
213 if (rc != EOK)
214 return rc;
215
216 ipart = capa->m / div;
217 fpart = capa->m % div;
218
219 sunit = cu_str[capa->cunit];
220 if (capa->dp > 0) {
221 ret = asprintf(rstr, "%" PRIu64 ".%0*" PRIu64 " %s", ipart,
222 (int)capa->dp, fpart, sunit);
223 } else {
224 ret = asprintf(rstr, "%" PRIu64 " %s", ipart, sunit);
225 }
226
227 if (ret < 0)
228 return ENOMEM;
229
230 return EOK;
231}
232
233static errno_t capa_digit_val(char c, int *val)
234{
235 switch (c) {
236 case '0':
237 *val = 0;
238 break;
239 case '1':
240 *val = 1;
241 break;
242 case '2':
243 *val = 2;
244 break;
245 case '3':
246 *val = 3;
247 break;
248 case '4':
249 *val = 4;
250 break;
251 case '5':
252 *val = 5;
253 break;
254 case '6':
255 *val = 6;
256 break;
257 case '7':
258 *val = 7;
259 break;
260 case '8':
261 *val = 8;
262 break;
263 case '9':
264 *val = 9;
265 break;
266 default:
267 return EINVAL;
268 }
269
270 return EOK;
271}
272
273errno_t capa_parse(const char *str, capa_spec_t *capa)
274{
275 const char *eptr;
276 const char *p;
277 int d;
278 int dp;
279 unsigned long m;
280 int i;
281
282 m = 0;
283
284 eptr = str;
285 while (capa_digit_val(*eptr, &d) == EOK) {
286 m = m * 10 + d;
287 ++eptr;
288 }
289
290 if (*eptr == '.') {
291 ++eptr;
292 dp = 0;
293 while (capa_digit_val(*eptr, &d) == EOK) {
294 m = m * 10 + d;
295 ++dp;
296 ++eptr;
297 }
298 } else {
299 dp = 0;
300 }
301
302 while (*eptr == ' ')
303 ++eptr;
304
305 if (*eptr == '\0') {
306 capa->cunit = cu_byte;
307 } else {
308 for (i = 0; i < CU_LIMIT; i++) {
309 if (str_lcasecmp(eptr, cu_str[i],
310 str_length(cu_str[i])) == 0) {
311 p = eptr + str_size(cu_str[i]);
312 while (*p == ' ')
313 ++p;
314 if (*p == '\0')
315 goto found;
316 }
317 }
318
319 return EINVAL;
320 found:
321 capa->cunit = i;
322 }
323
324 capa->m = m;
325 capa->dp = dp;
326 return EOK;
327}
328
329/** @}
330 */
Note: See TracBrowser for help on using the repository browser.