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