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".
This commit is contained in:
Manuel Novoa III 2001-06-30 07:40:44 +00:00
parent db15cb72e2
commit d877d44d12

View File

@ -1,66 +1,89 @@
/* vi: set sw=4 ts=4: */
/* /*
* make_human_readable_str * June 30, 2001 Manuel Novoa III
* *
* Copyright (C) 1999-2001 Erik Andersen <andersee@debian.org> * 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.
* *
* This program is free software; you can redistribute it and/or modify * Notes:
* it under the terms of the GNU General Public License as published by * 1) I'm using an unsigned long long to hold the product size * block_size,
* the Free Software Foundation; either version 2 of the License, or * as df (which calls this routine) could request a representation of a
* (at your option) any later version. * 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.
* *
* This program is distributed in the hope that it will be useful, * 2) The output of "ls -sh" can be misaligned because this routine always
* but WITHOUT ANY WARRANTY; without even the implied warranty of * outputs a decimal point and a tenths digit when display_unit != 0.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Hence, it isn't uncommon for the returned string to have a length
* General Public License for more details. * of 5 or 6 instead of <= 4 (as assumed).
* *
* You should have received a copy of the GNU General Public License * It might be nice to add a flag to indicate no decimal digits in
* along with this program; if not, write to the Free Software * that case. This could be either an additional parameter, or a
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * 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 <stdio.h>
#include "libbb.h" #include "libbb.h"
const char *make_human_readable_str(unsigned long size, const char *make_human_readable_str(unsigned long size,
unsigned long block_size, unsigned long display_unit) unsigned long block_size,
unsigned long display_unit)
{ {
static char str[10] = "\0"; /* The code will adjust for additional (appended) units. */
static const char strings[] = { 0, 'k', 'M', 'G', 'T', 0 }; 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";
if(size == 0 || block_size == 0) static char str[21]; /* Sufficient for 64 bit unsigned integers. */
return("0");
if(display_unit) { unsigned long long val;
snprintf(str, 9, "%ld", (size/display_unit)*block_size); int frac;
} else { const char *u;
/* Ok, looks like they want us to autoscale */ const char *f;
int i=0;
unsigned long divisor = 1;
long double result = size*block_size;
for(i=0; i <= 4; i++) {
divisor<<=10;
if (result <= divisor) {
divisor>>=10;
break;
}
}
result/=divisor;
if (result > 10)
snprintf(str, 9, "%.0Lf%c", result, strings[i]);
else
snprintf(str, 9, "%.1Lf%c", result, strings[i]);
}
return(str);
}
/* END CODE */ u = zero_and_units;
/* f = fmt;
Local Variables: frac = 0;
c-file-style: "linux"
c-basic-offset: 4 val = ((unsigned long long) size) * block_size;
tab-width: 4 if (val == 0) {
End: 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;
}