fmt_scaled.c 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /* $OpenBSD: fmt_scaled.c,v 1.17 2018/05/14 04:39:04 djm Exp $ */
  2. /*
  3. * Copyright (c) 2001, 2002, 2003 Ian F. Darwin. All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * 2. Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. * 3. The name of the author may not be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  17. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  18. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  19. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  20. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  21. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  22. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  23. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  25. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. /* OPENBSD ORIGINAL: lib/libutil/fmt_scaled.c */
  28. /*
  29. * fmt_scaled: Format numbers scaled for human comprehension
  30. * scan_scaled: Scan numbers in this format.
  31. *
  32. * "Human-readable" output uses 4 digits max, and puts a unit suffix at
  33. * the end. Makes output compact and easy-to-read esp. on huge disks.
  34. * Formatting code was originally in OpenBSD "df", converted to library routine.
  35. * Scanning code written for OpenBSD libutil.
  36. */
  37. #include "includes.h"
  38. #ifndef HAVE_FMT_SCALED
  39. #include <stdio.h>
  40. #include <stdlib.h>
  41. #include <errno.h>
  42. #include <string.h>
  43. #include <ctype.h>
  44. #include <limits.h>
  45. typedef enum {
  46. NONE = 0, KILO = 1, MEGA = 2, GIGA = 3, TERA = 4, PETA = 5, EXA = 6
  47. } unit_type;
  48. /* These three arrays MUST be in sync! XXX make a struct */
  49. static unit_type units[] = { NONE, KILO, MEGA, GIGA, TERA, PETA, EXA };
  50. static char scale_chars[] = "BKMGTPE";
  51. static long long scale_factors[] = {
  52. 1LL,
  53. 1024LL,
  54. 1024LL*1024,
  55. 1024LL*1024*1024,
  56. 1024LL*1024*1024*1024,
  57. 1024LL*1024*1024*1024*1024,
  58. 1024LL*1024*1024*1024*1024*1024,
  59. };
  60. #define SCALE_LENGTH (sizeof(units)/sizeof(units[0]))
  61. #define MAX_DIGITS (SCALE_LENGTH * 3) /* XXX strlen(sprintf("%lld", -1)? */
  62. /* Convert the given input string "scaled" into numeric in "result".
  63. * Return 0 on success, -1 and errno set on error.
  64. */
  65. int
  66. scan_scaled(char *scaled, long long *result)
  67. {
  68. char *p = scaled;
  69. int sign = 0;
  70. unsigned int i, ndigits = 0, fract_digits = 0;
  71. long long scale_fact = 1, whole = 0, fpart = 0;
  72. /* Skip leading whitespace */
  73. while (isascii((unsigned char)*p) && isspace((unsigned char)*p))
  74. ++p;
  75. /* Then at most one leading + or - */
  76. while (*p == '-' || *p == '+') {
  77. if (*p == '-') {
  78. if (sign) {
  79. errno = EINVAL;
  80. return -1;
  81. }
  82. sign = -1;
  83. ++p;
  84. } else if (*p == '+') {
  85. if (sign) {
  86. errno = EINVAL;
  87. return -1;
  88. }
  89. sign = +1;
  90. ++p;
  91. }
  92. }
  93. /* Main loop: Scan digits, find decimal point, if present.
  94. * We don't allow exponentials, so no scientific notation
  95. * (but note that E for Exa might look like e to some!).
  96. * Advance 'p' to end, to get scale factor.
  97. */
  98. for (; isascii((unsigned char)*p) &&
  99. (isdigit((unsigned char)*p) || *p=='.'); ++p) {
  100. if (*p == '.') {
  101. if (fract_digits > 0) { /* oops, more than one '.' */
  102. errno = EINVAL;
  103. return -1;
  104. }
  105. fract_digits = 1;
  106. continue;
  107. }
  108. i = (*p) - '0'; /* whew! finally a digit we can use */
  109. if (fract_digits > 0) {
  110. if (fract_digits >= MAX_DIGITS-1)
  111. /* ignore extra fractional digits */
  112. continue;
  113. fract_digits++; /* for later scaling */
  114. if (fpart > LLONG_MAX / 10) {
  115. errno = ERANGE;
  116. return -1;
  117. }
  118. fpart *= 10;
  119. if (i > LLONG_MAX - fpart) {
  120. errno = ERANGE;
  121. return -1;
  122. }
  123. fpart += i;
  124. } else { /* normal digit */
  125. if (++ndigits >= MAX_DIGITS) {
  126. errno = ERANGE;
  127. return -1;
  128. }
  129. if (whole > LLONG_MAX / 10) {
  130. errno = ERANGE;
  131. return -1;
  132. }
  133. whole *= 10;
  134. if (i > LLONG_MAX - whole) {
  135. errno = ERANGE;
  136. return -1;
  137. }
  138. whole += i;
  139. }
  140. }
  141. if (sign) {
  142. whole *= sign;
  143. fpart *= sign;
  144. }
  145. /* If no scale factor given, we're done. fraction is discarded. */
  146. if (!*p) {
  147. *result = whole;
  148. return 0;
  149. }
  150. /* Validate scale factor, and scale whole and fraction by it. */
  151. for (i = 0; i < SCALE_LENGTH; i++) {
  152. /* Are we there yet? */
  153. if (*p == scale_chars[i] ||
  154. *p == tolower((unsigned char)scale_chars[i])) {
  155. /* If it ends with alphanumerics after the scale char, bad. */
  156. if (isalnum((unsigned char)*(p+1))) {
  157. errno = EINVAL;
  158. return -1;
  159. }
  160. scale_fact = scale_factors[i];
  161. /* check for overflow and underflow after scaling */
  162. if (whole > LLONG_MAX / scale_fact ||
  163. whole < LLONG_MIN / scale_fact) {
  164. errno = ERANGE;
  165. return -1;
  166. }
  167. /* scale whole part */
  168. whole *= scale_fact;
  169. /* truncate fpart so it doesn't overflow.
  170. * then scale fractional part.
  171. */
  172. while (fpart >= LLONG_MAX / scale_fact) {
  173. fpart /= 10;
  174. fract_digits--;
  175. }
  176. fpart *= scale_fact;
  177. if (fract_digits > 0) {
  178. for (i = 0; i < fract_digits -1; i++)
  179. fpart /= 10;
  180. }
  181. whole += fpart;
  182. *result = whole;
  183. return 0;
  184. }
  185. }
  186. /* Invalid unit or character */
  187. errno = EINVAL;
  188. return -1;
  189. }
  190. /* Format the given "number" into human-readable form in "result".
  191. * Result must point to an allocated buffer of length FMT_SCALED_STRSIZE.
  192. * Return 0 on success, -1 and errno set if error.
  193. */
  194. int
  195. fmt_scaled(long long number, char *result)
  196. {
  197. long long abval, fract = 0;
  198. unsigned int i;
  199. unit_type unit = NONE;
  200. abval = llabs(number);
  201. /* Not every negative long long has a positive representation.
  202. * Also check for numbers that are just too darned big to format
  203. */
  204. if (abval < 0 || abval / 1024 >= scale_factors[SCALE_LENGTH-1]) {
  205. errno = ERANGE;
  206. return -1;
  207. }
  208. /* scale whole part; get unscaled fraction */
  209. for (i = 0; i < SCALE_LENGTH; i++) {
  210. if (abval/1024 < scale_factors[i]) {
  211. unit = units[i];
  212. fract = (i == 0) ? 0 : abval % scale_factors[i];
  213. number /= scale_factors[i];
  214. if (i > 0)
  215. fract /= scale_factors[i - 1];
  216. break;
  217. }
  218. }
  219. fract = (10 * fract + 512) / 1024;
  220. /* if the result would be >= 10, round main number */
  221. if (fract >= 10) {
  222. if (number >= 0)
  223. number++;
  224. else
  225. number--;
  226. fract = 0;
  227. } else if (fract < 0) {
  228. /* shouldn't happen */
  229. fract = 0;
  230. }
  231. if (number == 0)
  232. strlcpy(result, "0B", FMT_SCALED_STRSIZE);
  233. else if (unit == NONE || number >= 100 || number <= -100) {
  234. if (fract >= 5) {
  235. if (number >= 0)
  236. number++;
  237. else
  238. number--;
  239. }
  240. (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld%c",
  241. number, scale_chars[unit]);
  242. } else
  243. (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld.%1lld%c",
  244. number, fract, scale_chars[unit]);
  245. return 0;
  246. }
  247. #ifdef MAIN
  248. /*
  249. * This is the original version of the program in the man page.
  250. * Copy-and-paste whatever you need from it.
  251. */
  252. int
  253. main(int argc, char **argv)
  254. {
  255. char *cinput = "1.5K", buf[FMT_SCALED_STRSIZE];
  256. long long ninput = 10483892, result;
  257. if (scan_scaled(cinput, &result) == 0)
  258. printf("\"%s\" -> %lld\n", cinput, result);
  259. else
  260. perror(cinput);
  261. if (fmt_scaled(ninput, buf) == 0)
  262. printf("%lld -> \"%s\"\n", ninput, buf);
  263. else
  264. fprintf(stderr, "%lld invalid (%s)\n", ninput, strerror(errno));
  265. return 0;
  266. }
  267. #endif
  268. #endif /* HAVE_FMT_SCALED */