Source code pulled from OpenBSD for OpenNTPD. The place to contribute to this code is via the OpenBSD CVS tree.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
7.1 KiB

20 years ago
  1. /* $OpenBSD: fmt_scaled.c,v 1.12 2013/11/29 19:00:51 deraadt 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. /*
  28. * fmt_scaled: Format numbers scaled for human comprehension
  29. * scan_scaled: Scan numbers in this format.
  30. *
  31. * "Human-readable" output uses 4 digits max, and puts a unit suffix at
  32. * the end. Makes output compact and easy-to-read esp. on huge disks.
  33. * Formatting code was originally in OpenBSD "df", converted to library routine.
  34. * Scanning code written for OpenBSD libutil.
  35. */
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <errno.h>
  39. #include <string.h>
  40. #include <ctype.h>
  41. #include <limits.h>
  42. #include "util.h"
  43. typedef enum {
  44. NONE = 0, KILO = 1, MEGA = 2, GIGA = 3, TERA = 4, PETA = 5, EXA = 6
  45. } unit_type;
  46. /* These three arrays MUST be in sync! XXX make a struct */
  47. static unit_type units[] = { NONE, KILO, MEGA, GIGA, TERA, PETA, EXA };
  48. static char scale_chars[] = "BKMGTPE";
  49. static long long scale_factors[] = {
  50. 1LL,
  51. 1024LL,
  52. 1024LL*1024,
  53. 1024LL*1024*1024,
  54. 1024LL*1024*1024*1024,
  55. 1024LL*1024*1024*1024*1024,
  56. 1024LL*1024*1024*1024*1024*1024,
  57. };
  58. #define SCALE_LENGTH (sizeof(units)/sizeof(units[0]))
  59. #define MAX_DIGITS (SCALE_LENGTH * 3) /* XXX strlen(sprintf("%lld", -1)? */
  60. /* Convert the given input string "scaled" into numeric in "result".
  61. * Return 0 on success, -1 and errno set on error.
  62. */
  63. int
  64. scan_scaled(char *scaled, long long *result)
  65. {
  66. char *p = scaled;
  67. int sign = 0;
  68. unsigned int i, ndigits = 0, fract_digits = 0;
  69. long long scale_fact = 1, whole = 0, fpart = 0;
  70. /* Skip leading whitespace */
  71. while (isascii((unsigned char)*p) && isspace((unsigned char)*p))
  72. ++p;
  73. /* Then at most one leading + or - */
  74. while (*p == '-' || *p == '+') {
  75. if (*p == '-') {
  76. if (sign) {
  77. errno = EINVAL;
  78. return -1;
  79. }
  80. sign = -1;
  81. ++p;
  82. } else if (*p == '+') {
  83. if (sign) {
  84. errno = EINVAL;
  85. return -1;
  86. }
  87. sign = +1;
  88. ++p;
  89. }
  90. }
  91. /* Main loop: Scan digits, find decimal point, if present.
  92. * We don't allow exponentials, so no scientific notation
  93. * (but note that E for Exa might look like e to some!).
  94. * Advance 'p' to end, to get scale factor.
  95. */
  96. for (; isascii((unsigned char)*p) &&
  97. (isdigit((unsigned char)*p) || *p=='.'); ++p) {
  98. if (*p == '.') {
  99. if (fract_digits > 0) { /* oops, more than one '.' */
  100. errno = EINVAL;
  101. return -1;
  102. }
  103. fract_digits = 1;
  104. continue;
  105. }
  106. i = (*p) - '0'; /* whew! finally a digit we can use */
  107. if (fract_digits > 0) {
  108. if (fract_digits >= MAX_DIGITS-1)
  109. /* ignore extra fractional digits */
  110. continue;
  111. fract_digits++; /* for later scaling */
  112. fpart *= 10;
  113. fpart += i;
  114. } else { /* normal digit */
  115. if (++ndigits >= MAX_DIGITS) {
  116. errno = ERANGE;
  117. return -1;
  118. }
  119. whole *= 10;
  120. whole += i;
  121. }
  122. }
  123. if (sign) {
  124. whole *= sign;
  125. fpart *= sign;
  126. }
  127. /* If no scale factor given, we're done. fraction is discarded. */
  128. if (!*p) {
  129. *result = whole;
  130. return 0;
  131. }
  132. /* Validate scale factor, and scale whole and fraction by it. */
  133. for (i = 0; i < SCALE_LENGTH; i++) {
  134. /* Are we there yet? */
  135. if (*p == scale_chars[i] ||
  136. *p == tolower((unsigned char)scale_chars[i])) {
  137. /* If it ends with alphanumerics after the scale char, bad. */
  138. if (isalnum((unsigned char)*(p+1))) {
  139. errno = EINVAL;
  140. return -1;
  141. }
  142. scale_fact = scale_factors[i];
  143. /* scale whole part */
  144. whole *= scale_fact;
  145. /* truncate fpart so it does't overflow.
  146. * then scale fractional part.
  147. */
  148. while (fpart >= LLONG_MAX / scale_fact) {
  149. fpart /= 10;
  150. fract_digits--;
  151. }
  152. fpart *= scale_fact;
  153. if (fract_digits > 0) {
  154. for (i = 0; i < fract_digits -1; i++)
  155. fpart /= 10;
  156. }
  157. whole += fpart;
  158. *result = whole;
  159. return 0;
  160. }
  161. }
  162. /* Invalid unit or character */
  163. errno = EINVAL;
  164. return -1;
  165. }
  166. /* Format the given "number" into human-readable form in "result".
  167. * Result must point to an allocated buffer of length FMT_SCALED_STRSIZE.
  168. * Return 0 on success, -1 and errno set if error.
  169. */
  170. int
  171. fmt_scaled(long long number, char *result)
  172. {
  173. long long abval, fract = 0;
  174. unsigned int i;
  175. unit_type unit = NONE;
  176. abval = llabs(number);
  177. /* Not every negative long long has a positive representation.
  178. * Also check for numbers that are just too darned big to format
  179. */
  180. if (abval < 0 || abval / 1024 >= scale_factors[SCALE_LENGTH-1]) {
  181. errno = ERANGE;
  182. return -1;
  183. }
  184. /* scale whole part; get unscaled fraction */
  185. for (i = 0; i < SCALE_LENGTH; i++) {
  186. if (abval/1024 < scale_factors[i]) {
  187. unit = units[i];
  188. fract = (i == 0) ? 0 : abval % scale_factors[i];
  189. number /= scale_factors[i];
  190. if (i > 0)
  191. fract /= scale_factors[i - 1];
  192. break;
  193. }
  194. }
  195. fract = (10 * fract + 512) / 1024;
  196. /* if the result would be >= 10, round main number */
  197. if (fract == 10) {
  198. if (number >= 0)
  199. number++;
  200. else
  201. number--;
  202. fract = 0;
  203. }
  204. if (number == 0)
  205. strlcpy(result, "0B", FMT_SCALED_STRSIZE);
  206. else if (unit == NONE || number >= 100 || number <= -100) {
  207. if (fract >= 5) {
  208. if (number >= 0)
  209. number++;
  210. else
  211. number--;
  212. }
  213. (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld%c",
  214. number, scale_chars[unit]);
  215. } else
  216. (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld.%1lld%c",
  217. number, fract, scale_chars[unit]);
  218. return 0;
  219. }
  220. #ifdef MAIN
  221. /*
  222. * This is the original version of the program in the man page.
  223. * Copy-and-paste whatever you need from it.
  224. */
  225. int
  226. main(int argc, char **argv)
  227. {
  228. char *cinput = "1.5K", buf[FMT_SCALED_STRSIZE];
  229. long long ninput = 10483892, result;
  230. if (scan_scaled(cinput, &result) == 0)
  231. printf("\"%s\" -> %lld\n", cinput, result);
  232. else
  233. perror(cinput);
  234. if (fmt_scaled(ninput, buf) == 0)
  235. printf("%lld -> \"%s\"\n", ninput, buf);
  236. else
  237. fprintf(stderr, "%lld invalid (%s)\n", ninput, strerror(errno));
  238. return 0;
  239. }
  240. #endif