71d65e04fe7730a777e5eb249b4a9e043cb944d6
[fio.git] / lib / num2str.c
1 #include <assert.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5
6 #include "../compiler/compiler.h"
7 #include "num2str.h"
8
9 #define ARRAY_SIZE(x)    (sizeof((x)) / (sizeof((x)[0])))
10
11 /**
12  * num2str() - Cheesy number->string conversion, complete with carry rounding error.
13  * @num: quantity (e.g., number of blocks, bytes or bits)
14  * @maxlen: max number of digits in the output string (not counting prefix and units, but counting .)
15  * @base: multiplier for num (e.g., if num represents Ki, use 1024)
16  * @pow2: select unit prefix - 0=power-of-10 decimal SI, nonzero=power-of-2 binary IEC
17  * @units: select units - N2S_* constants defined in num2str.h
18  * @returns a malloc'd buffer containing "number[<unit prefix>][<units>]"
19  */
20 char *num2str(uint64_t num, int maxlen, int base, int pow2, enum n2s_unit units)
21 {
22         const char *sistr[] = { "", "k", "M", "G", "T", "P" };
23         const char *iecstr[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi" };
24         const char **unitprefix;
25         const char *unitstr[] = { "", "/s", "B", "bit", "B/s", "bit/s" };
26         const unsigned int thousand[] = { 1000, 1024 };
27         unsigned int modulo;
28         int unit_index = 0, post_index, carry = 0;
29         char tmp[32], fmt[32];
30         char *buf;
31
32         compiletime_assert(sizeof(sistr) == sizeof(iecstr), "unit prefix arrays must be identical sizes");
33
34         buf = malloc(128);
35         if (!buf)
36                 return NULL;
37
38         if (pow2)
39                 unitprefix = iecstr;
40         else
41                 unitprefix = sistr;
42
43         for (post_index = 0; base > 1; post_index++)
44                 base /= thousand[!!pow2];
45
46         switch (units) {
47         case N2S_NONE:
48                 break;
49         case N2S_PERSEC:
50                 unit_index = 1;
51                 break;
52         case N2S_BYTE:
53                 unit_index = 2;
54                 break;
55         case N2S_BIT:
56                 unit_index = 3;
57                 num *= 8;
58                 break;
59         case N2S_BYTEPERSEC:
60                 unit_index = 4;
61                 break;
62         case N2S_BITPERSEC:
63                 unit_index = 5;
64                 num *= 8;
65                 break;
66         }
67
68         /*
69          * Divide by K/Ki until string length of num <= maxlen.
70          */
71         modulo = -1U;
72         while (post_index < sizeof(sistr)) {
73                 sprintf(tmp, "%llu", (unsigned long long) num);
74                 if (strlen(tmp) <= maxlen)
75                         break;
76
77                 modulo = num % thousand[!!pow2];
78                 num /= thousand[!!pow2];
79                 carry = modulo >= thousand[!!pow2] / 2;
80                 post_index++;
81         }
82
83         /*
84          * If no modulo, then we're done.
85          */
86         if (modulo == -1U) {
87 done:
88                 if (post_index >= ARRAY_SIZE(sistr))
89                         post_index = 0;
90
91                 sprintf(buf, "%llu%s%s", (unsigned long long) num,
92                         unitprefix[post_index], unitstr[unit_index]);
93                 return buf;
94         }
95
96         /*
97          * If no room for decimals, then we're done.
98          */
99         sprintf(tmp, "%llu", (unsigned long long) num);
100         if ((int)(maxlen - strlen(tmp)) <= 1) {
101                 if (carry)
102                         num++;
103                 goto done;
104         }
105
106         /*
107          * Fill in everything and return the result.
108          */
109         assert(maxlen - strlen(tmp) - 1 > 0);
110         assert(modulo < thousand[!!pow2]);
111         sprintf(fmt, "%%.%df", (int)(maxlen - strlen(tmp) - 1));
112         sprintf(tmp, fmt, (double)modulo / (double)thousand[!!pow2]);
113
114         sprintf(buf, "%llu.%s%s%s", (unsigned long long) num, &tmp[2],
115                         unitprefix[post_index], unitstr[unit_index]);
116         return buf;
117 }