hush/libbb/human_readable.c
Manuel Novoa III d877d44d12 All-integer version (but it does use an unsigned long long) which fixes
the problems of the previous version (used floating point, overflowed, didn't
round properly).  The comments at the top of the file are worth reading;
especially note 2 concerning "ls -sh".
2001-06-30 07:40:44 +00:00

90 lines
2.6 KiB
C

/*
* June 30, 2001 Manuel Novoa III
*
* All-integer version (hey, not everyone has floating point) of
* make_human_readable_str, modified from similar code I had written
* for busybox several months ago.
*
* Notes:
* 1) I'm using an unsigned long long to hold the product size * block_size,
* as df (which calls this routine) could request a representation of a
* partition size in bytes > max of unsigned long. If long longs aren't
* available, it would be possible to do what's needed using polynomial
* representations (say, powers of 1024) and manipulating coefficients.
* The base ten "bytes" output could be handled similarly.
*
* 2) The output of "ls -sh" can be misaligned because this routine always
* outputs a decimal point and a tenths digit when display_unit != 0.
* Hence, it isn't uncommon for the returned string to have a length
* of 5 or 6 instead of <= 4 (as assumed).
*
* It might be nice to add a flag to indicate no decimal digits in
* that case. This could be either an additional parameter, or a
* special value of display_unit. Such a flag would also be nice for du.
*
* Some code to omit the decimal point and tenths digit is sketched out
* and "#if 0"'d below.
*/
#include <stdio.h>
#include "libbb.h"
const char *make_human_readable_str(unsigned long size,
unsigned long block_size,
unsigned long display_unit)
{
/* The code will adjust for additional (appended) units. */
static const char zero_and_units[] = { '0', 0, 'k', 'M', 'G', 'T' };
static const char fmt[] = "%Lu";
static const char fmt_tenths[] = "%Lu.%d%c";
static char str[21]; /* Sufficient for 64 bit unsigned integers. */
unsigned long long val;
int frac;
const char *u;
const char *f;
u = zero_and_units;
f = fmt;
frac = 0;
val = ((unsigned long long) size) * block_size;
if (val == 0) {
return u;
}
if (display_unit) {
val += display_unit/2; /* Deal with rounding. */
val /= display_unit; /* Don't combine with the line above!!! */
} else {
++u;
while ((val >= KILOBYTE)
&& (u < zero_and_units + sizeof(zero_and_units) - 1)) {
f = fmt_tenths;
++u;
frac = ((((int)(val % KILOBYTE)) * 10) + (KILOBYTE/2)) / KILOBYTE;
val /= KILOBYTE;
}
if (frac >= 10) { /* We need to round up here. */
++val;
frac = 0;
}
#if 0
/* Sample code to omit decimal point and tenths digit. */
if ( /* no_tenths */ 1 ) {
if ( frac >= 5 ) {
++val;
}
f = "%Lu%*c" /* fmt_no_tenths */ ;
frac = 1;
}
#endif
}
/* If f==fmt then 'frac' and 'u' are ignored. */
snprintf(str, sizeof(str), f, val, frac, *u);
return str;
}