114 lines
2.8 KiB
C
114 lines
2.8 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "../compiler/compiler.h"
|
|
#include "num2str.h"
|
|
|
|
#define ARRAY_SIZE(x) (sizeof((x)) / (sizeof((x)[0])))
|
|
|
|
/**
|
|
* num2str() - Cheesy number->string conversion, complete with carry rounding error.
|
|
* @num: quantity (e.g., number of blocks, bytes or bits)
|
|
* @maxlen: max number of digits in the output string (not counting prefix and units, but counting .)
|
|
* @base: multiplier for num (e.g., if num represents Ki, use 1024)
|
|
* @pow2: select unit prefix - 0=power-of-10 decimal SI, nonzero=power-of-2 binary IEC
|
|
* @units: select units - N2S_* macros defined in num2str.h
|
|
* @returns a malloc'd buffer containing "number[<unit prefix>][<units>]"
|
|
*/
|
|
char *num2str(uint64_t num, int maxlen, int base, int pow2, int units)
|
|
{
|
|
const char *sistr[] = { "", "k", "M", "G", "T", "P" };
|
|
const char *iecstr[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi" };
|
|
const char **unitprefix;
|
|
const char *unitstr[] = { "", "/s", "B", "bit", "B/s", "bit/s" };
|
|
const unsigned int thousand[] = { 1000, 1024 };
|
|
unsigned int modulo;
|
|
int unit_index = 0, post_index, carry = 0;
|
|
char tmp[32], fmt[32];
|
|
char *buf;
|
|
|
|
compiletime_assert(sizeof(sistr) == sizeof(iecstr), "unit prefix arrays must be identical sizes");
|
|
|
|
buf = malloc(128);
|
|
if (!buf)
|
|
return NULL;
|
|
|
|
if (pow2)
|
|
unitprefix = iecstr;
|
|
else
|
|
unitprefix = sistr;
|
|
|
|
for (post_index = 0; base > 1; post_index++)
|
|
base /= thousand[!!pow2];
|
|
|
|
switch (units) {
|
|
case N2S_PERSEC:
|
|
unit_index = 1;
|
|
break;
|
|
case N2S_BYTE:
|
|
unit_index = 2;
|
|
break;
|
|
case N2S_BIT:
|
|
unit_index = 3;
|
|
num *= 8;
|
|
break;
|
|
case N2S_BYTEPERSEC:
|
|
unit_index = 4;
|
|
break;
|
|
case N2S_BITPERSEC:
|
|
unit_index = 5;
|
|
num *= 8;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Divide by K/Ki until string length of num <= maxlen.
|
|
*/
|
|
modulo = -1U;
|
|
while (post_index < sizeof(sistr)) {
|
|
sprintf(tmp, "%llu", (unsigned long long) num);
|
|
if (strlen(tmp) <= maxlen)
|
|
break;
|
|
|
|
modulo = num % thousand[!!pow2];
|
|
num /= thousand[!!pow2];
|
|
carry = modulo >= thousand[!!pow2] / 2;
|
|
post_index++;
|
|
}
|
|
|
|
/*
|
|
* If no modulo, then we're done.
|
|
*/
|
|
if (modulo == -1U) {
|
|
done:
|
|
if (post_index >= ARRAY_SIZE(sistr))
|
|
post_index = 0;
|
|
|
|
sprintf(buf, "%llu%s%s", (unsigned long long) num,
|
|
unitprefix[post_index], unitstr[unit_index]);
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* If no room for decimals, then we're done.
|
|
*/
|
|
sprintf(tmp, "%llu", (unsigned long long) num);
|
|
if ((int)(maxlen - strlen(tmp)) <= 1) {
|
|
if (carry)
|
|
num++;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Fill in everything and return the result.
|
|
*/
|
|
assert(maxlen - strlen(tmp) - 1 > 0);
|
|
assert(modulo < thousand[!!pow2]);
|
|
sprintf(fmt, "%%.%df", (int)(maxlen - strlen(tmp) - 1));
|
|
sprintf(tmp, fmt, (double)modulo / (double)thousand[!!pow2]);
|
|
|
|
sprintf(buf, "%llu.%s%s%s", (unsigned long long) num, &tmp[2],
|
|
unitprefix[post_index], unitstr[unit_index]);
|
|
return buf;
|
|
}
|