fmt_scaled.c revision 181111
1130812Smarcel/* $OpenBSD: fmt_scaled.c,v 1.9 2007/03/20 03:42:52 tedu Exp $ */ 2130812Smarcel 3130812Smarcel/* 4130812Smarcel * Copyright (c) 2001, 2002, 2003 Ian F. Darwin. All rights reserved. 5130812Smarcel * 6130812Smarcel * Redistribution and use in source and binary forms, with or without 7130812Smarcel * modification, are permitted provided that the following conditions 8130812Smarcel * are met: 9130812Smarcel * 1. Redistributions of source code must retain the above copyright 10130812Smarcel * notice, this list of conditions and the following disclaimer. 11130812Smarcel * 2. Redistributions in binary form must reproduce the above copyright 12130812Smarcel * notice, this list of conditions and the following disclaimer in the 13130812Smarcel * documentation and/or other materials provided with the distribution. 14130812Smarcel * 3. The name of the author may not be used to endorse or promote products 15130812Smarcel * derived from this software without specific prior written permission. 16130812Smarcel * 17130812Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18130812Smarcel * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19130812Smarcel * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20130812Smarcel * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21130812Smarcel * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22130812Smarcel * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23130812Smarcel * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24130812Smarcel * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25130812Smarcel * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26130812Smarcel * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27130812Smarcel */ 28130812Smarcel 29130812Smarcel/* OPENBSD ORIGINAL: lib/libutil/fmt_scaled.c */ 30130812Smarcel 31130812Smarcel/* 32130812Smarcel * fmt_scaled: Format numbers scaled for human comprehension 33130812Smarcel * scan_scaled: Scan numbers in this format. 34130812Smarcel * 35130812Smarcel * "Human-readable" output uses 4 digits max, and puts a unit suffix at 36130812Smarcel * the end. Makes output compact and easy-to-read esp. on huge disks. 37130812Smarcel * Formatting code was originally in OpenBSD "df", converted to library routine. 38130812Smarcel * Scanning code written for OpenBSD libutil. 39130812Smarcel */ 40130812Smarcel 41130812Smarcel#include "includes.h" 42130812Smarcel 43130812Smarcel#ifndef HAVE_FMT_SCALED 44130812Smarcel 45130812Smarcel#include <stdio.h> 46130812Smarcel#include <stdlib.h> 47130812Smarcel#include <errno.h> 48130812Smarcel#include <string.h> 49130812Smarcel#include <ctype.h> 50130812Smarcel#include <limits.h> 51130812Smarcel 52130812Smarceltypedef enum { 53130812Smarcel NONE = 0, KILO = 1, MEGA = 2, GIGA = 3, TERA = 4, PETA = 5, EXA = 6 54130812Smarcel} unit_type; 55130812Smarcel 56130812Smarcel/* These three arrays MUST be in sync! XXX make a struct */ 57130812Smarcelstatic unit_type units[] = { NONE, KILO, MEGA, GIGA, TERA, PETA, EXA }; 58130812Smarcelstatic char scale_chars[] = "BKMGTPE"; 59130812Smarcelstatic long long scale_factors[] = { 60130812Smarcel 1LL, 61130812Smarcel 1024LL, 62130812Smarcel 1024LL*1024, 63130812Smarcel 1024LL*1024*1024, 64130812Smarcel 1024LL*1024*1024*1024, 65130812Smarcel 1024LL*1024*1024*1024*1024, 66130812Smarcel 1024LL*1024*1024*1024*1024*1024, 67130812Smarcel}; 68130812Smarcel#define SCALE_LENGTH (sizeof(units)/sizeof(units[0])) 69130812Smarcel 70130812Smarcel#define MAX_DIGITS (SCALE_LENGTH * 3) /* XXX strlen(sprintf("%lld", -1)? */ 71130812Smarcel 72130812Smarcel/** Convert the given input string "scaled" into numeric in "result". 73130812Smarcel * Return 0 on success, -1 and errno set on error. 74130812Smarcel */ 75130812Smarcelint 76130812Smarcelscan_scaled(char *scaled, long long *result) 77130812Smarcel{ 78130812Smarcel char *p = scaled; 79130812Smarcel int sign = 0; 80130812Smarcel unsigned int i, ndigits = 0, fract_digits = 0; 81130812Smarcel long long scale_fact = 1, whole = 0, fpart = 0; 82130812Smarcel 83130812Smarcel /* Skip leading whitespace */ 84130812Smarcel while (isascii(*p) && isspace(*p)) 85130812Smarcel ++p; 86130812Smarcel 87130812Smarcel /* Then at most one leading + or - */ 88130812Smarcel while (*p == '-' || *p == '+') { 89130812Smarcel if (*p == '-') { 90130812Smarcel if (sign) { 91130812Smarcel errno = EINVAL; 92130812Smarcel return -1; 93130812Smarcel } 94130812Smarcel sign = -1; 95130812Smarcel ++p; 96130812Smarcel } else if (*p == '+') { 97130812Smarcel if (sign) { 98130812Smarcel errno = EINVAL; 99130812Smarcel return -1; 100130812Smarcel } 101130812Smarcel sign = +1; 102130812Smarcel ++p; 103130812Smarcel } 104130812Smarcel } 105130812Smarcel 106130812Smarcel /* Main loop: Scan digits, find decimal point, if present. 107130812Smarcel * We don't allow exponentials, so no scientific notation 108130812Smarcel * (but note that E for Exa might look like e to some!). 109130812Smarcel * Advance 'p' to end, to get scale factor. 110130812Smarcel */ 111130812Smarcel for (; isascii(*p) && (isdigit(*p) || *p=='.'); ++p) { 112130812Smarcel if (*p == '.') { 113130812Smarcel if (fract_digits > 0) { /* oops, more than one '.' */ 114130812Smarcel errno = EINVAL; 115130812Smarcel return -1; 116130812Smarcel } 117130812Smarcel fract_digits = 1; 118130812Smarcel continue; 119130812Smarcel } 120130812Smarcel 121130812Smarcel i = (*p) - '0'; /* whew! finally a digit we can use */ 122130812Smarcel if (fract_digits > 0) { 123130812Smarcel if (fract_digits >= MAX_DIGITS-1) 124130812Smarcel /* ignore extra fractional digits */ 125130812Smarcel continue; 126130812Smarcel fract_digits++; /* for later scaling */ 127130812Smarcel fpart *= 10; 128130812Smarcel fpart += i; 129130812Smarcel } else { /* normal digit */ 130130812Smarcel if (++ndigits >= MAX_DIGITS) { 131130812Smarcel errno = ERANGE; 132130812Smarcel return -1; 133130812Smarcel } 134130812Smarcel whole *= 10; 135130812Smarcel whole += i; 136130812Smarcel } 137130812Smarcel } 138130812Smarcel 139130812Smarcel if (sign) { 140130812Smarcel whole *= sign; 141130812Smarcel fpart *= sign; 142130812Smarcel } 143130812Smarcel 144130812Smarcel /* If no scale factor given, we're done. fraction is discarded. */ 145130812Smarcel if (!*p) { 146130812Smarcel *result = whole; 147130812Smarcel return 0; 148130812Smarcel } 149130812Smarcel 150130812Smarcel /* Validate scale factor, and scale whole and fraction by it. */ 151130812Smarcel for (i = 0; i < SCALE_LENGTH; i++) { 152130812Smarcel 153130812Smarcel /** Are we there yet? */ 154130812Smarcel if (*p == scale_chars[i] || 155130812Smarcel *p == tolower(scale_chars[i])) { 156130812Smarcel 157130812Smarcel /* If it ends with alphanumerics after the scale char, bad. */ 158130812Smarcel if (isalnum(*(p+1))) { 159130812Smarcel errno = EINVAL; 160130812Smarcel return -1; 161130812Smarcel } 162130812Smarcel scale_fact = scale_factors[i]; 163130812Smarcel 164130812Smarcel /* scale whole part */ 165130812Smarcel whole *= scale_fact; 166130812Smarcel 167130812Smarcel /* truncate fpart so it does't overflow. 168130812Smarcel * then scale fractional part. 169130812Smarcel */ 170130812Smarcel while (fpart >= LLONG_MAX / scale_fact) { 171130812Smarcel fpart /= 10; 172130812Smarcel fract_digits--; 173130812Smarcel } 174130812Smarcel fpart *= scale_fact; 175130812Smarcel if (fract_digits > 0) { 176130812Smarcel for (i = 0; i < fract_digits -1; i++) 177130812Smarcel fpart /= 10; 178130812Smarcel } 179130812Smarcel whole += fpart; 180130812Smarcel *result = whole; 181130812Smarcel return 0; 182130812Smarcel } 183130812Smarcel } 184130812Smarcel errno = ERANGE; 185130812Smarcel return -1; 186130812Smarcel} 187130812Smarcel 188130812Smarcel/* Format the given "number" into human-readable form in "result". 189130812Smarcel * Result must point to an allocated buffer of length FMT_SCALED_STRSIZE. 190130812Smarcel * Return 0 on success, -1 and errno set if error. 191130812Smarcel */ 192130812Smarcelint 193130812Smarcelfmt_scaled(long long number, char *result) 194130812Smarcel{ 195130812Smarcel long long abval, fract = 0; 196130812Smarcel unsigned int i; 197130812Smarcel unit_type unit = NONE; 198130812Smarcel 199130812Smarcel abval = (number < 0LL) ? -number : number; /* no long long_abs yet */ 200130812Smarcel 201130812Smarcel /* Not every negative long long has a positive representation. 202130812Smarcel * Also check for numbers that are just too darned big to format 203130812Smarcel */ 204130812Smarcel if (abval < 0 || abval / 1024 >= scale_factors[SCALE_LENGTH-1]) { 205130812Smarcel errno = ERANGE; 206130812Smarcel return -1; 207130812Smarcel } 208130812Smarcel 209130812Smarcel /* scale whole part; get unscaled fraction */ 210130812Smarcel for (i = 0; i < SCALE_LENGTH; i++) { 211130812Smarcel if (abval/1024 < scale_factors[i]) { 212130812Smarcel unit = units[i]; 213130812Smarcel fract = (i == 0) ? 0 : abval % scale_factors[i]; 214130812Smarcel number /= scale_factors[i]; 215130812Smarcel if (i > 0) 216130812Smarcel fract /= scale_factors[i - 1]; 217130812Smarcel break; 218130812Smarcel } 219130812Smarcel } 220130812Smarcel 221130812Smarcel fract = (10 * fract + 512) / 1024; 222130812Smarcel /* if the result would be >= 10, round main number */ 223130812Smarcel if (fract == 10) { 224130812Smarcel if (number >= 0) 225130812Smarcel number++; 226130812Smarcel else 227130812Smarcel number--; 228130812Smarcel fract = 0; 229130812Smarcel } 230130812Smarcel 231130812Smarcel if (number == 0) 232130812Smarcel strlcpy(result, "0B", FMT_SCALED_STRSIZE); 233130812Smarcel else if (unit == NONE || number >= 100 || number <= -100) { 234130812Smarcel if (fract >= 5) { 235130812Smarcel if (number >= 0) 236130812Smarcel number++; 237130812Smarcel else 238130812Smarcel number--; 239130812Smarcel } 240130812Smarcel (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld%c", 241130812Smarcel number, scale_chars[unit]); 242130812Smarcel } else 243130812Smarcel (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld.%1lld%c", 244130812Smarcel number, fract, scale_chars[unit]); 245130812Smarcel 246130812Smarcel return 0; 247130812Smarcel} 248130812Smarcel 249130812Smarcel#ifdef MAIN 250130812Smarcel/* 251130812Smarcel * This is the original version of the program in the man page. 252130812Smarcel * Copy-and-paste whatever you need from it. 253130812Smarcel */ 254130812Smarcelint 255130812Smarcelmain(int argc, char **argv) 256130812Smarcel{ 257130812Smarcel char *cinput = "1.5K", buf[FMT_SCALED_STRSIZE]; 258130812Smarcel long long ninput = 10483892, result; 259130812Smarcel 260130812Smarcel if (scan_scaled(cinput, &result) == 0) 261 printf("\"%s\" -> %lld\n", cinput, result); 262 else 263 perror(cinput); 264 265 if (fmt_scaled(ninput, buf) == 0) 266 printf("%lld -> \"%s\"\n", ninput, buf); 267 else 268 fprintf(stderr, "%lld invalid (%s)\n", ninput, strerror(errno)); 269 270 return 0; 271} 272#endif 273 274#endif /* HAVE_FMT_SCALED */ 275