summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorManuel Novoa III <mjn3@codepoet.org>2001-06-30 07:40:44 +0000
committerManuel Novoa III <mjn3@codepoet.org>2001-06-30 07:40:44 +0000
commitd877d44d127cb58181f8f8886675406fb99cc6aa (patch)
tree899a3ea644af7ce0faa19524e7fab91f0d9152fe
parentdb15cb72e2dad0e2dbed77665ac848a49b950038 (diff)
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".
-rw-r--r--libbb/human_readable.c117
1 files changed, 70 insertions, 47 deletions
diff --git a/libbb/human_readable.c b/libbb/human_readable.c
index 2cb887563..548712c75 100644
--- a/libbb/human_readable.c
+++ b/libbb/human_readable.c
@@ -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
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * 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.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
+ * 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).
*
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * 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)
+ unsigned long block_size,
+ unsigned long display_unit)
{
- static char str[10] = "\0";
- static const char strings[] = { 0, 'k', 'M', 'G', 'T', 0 };
+ /* 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";
- if(size == 0 || block_size == 0)
- return("0");
+ static char str[21]; /* Sufficient for 64 bit unsigned integers. */
- if(display_unit) {
- snprintf(str, 9, "%ld", (size/display_unit)*block_size);
+ 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 {
- /* Ok, looks like they want us to autoscale */
- 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;
+ ++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;
}
- result/=divisor;
- if (result > 10)
- snprintf(str, 9, "%.0Lf%c", result, strings[i]);
- else
- snprintf(str, 9, "%.1Lf%c", result, strings[i]);
+#endif
}
- return(str);
-}
-/* END CODE */
-/*
-Local Variables:
-c-file-style: "linux"
-c-basic-offset: 4
-tab-width: 4
-End:
-*/
+ /* If f==fmt then 'frac' and 'u' are ignored. */
+ snprintf(str, sizeof(str), f, val, frac, *u);
+
+ return str;
+}