1194794Sdelphij/*	$NetBSD: humanize_number.c,v 1.14 2008/04/28 20:22:59 martin Exp $	*/
2129677Spjd
3129677Spjd/*
4129677Spjd * Copyright (c) 1997, 1998, 1999, 2002 The NetBSD Foundation, Inc.
5256130Sjmg * Copyright 2013 John-Mark Gurney <jmg@FreeBSD.org>
6129677Spjd * All rights reserved.
7129677Spjd *
8129677Spjd * This code is derived from software contributed to The NetBSD Foundation
9129677Spjd * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
10129677Spjd * NASA Ames Research Center, by Luke Mewburn and by Tomas Svensson.
11129677Spjd *
12129677Spjd * Redistribution and use in source and binary forms, with or without
13129677Spjd * modification, are permitted provided that the following conditions
14129677Spjd * are met:
15129677Spjd * 1. Redistributions of source code must retain the above copyright
16129677Spjd *    notice, this list of conditions and the following disclaimer.
17129677Spjd * 2. Redistributions in binary form must reproduce the above copyright
18129677Spjd *    notice, this list of conditions and the following disclaimer in the
19129677Spjd *    documentation and/or other materials provided with the distribution.
20129677Spjd *
21129677Spjd * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
22129677Spjd * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23129677Spjd * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24129677Spjd * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
25129677Spjd * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26129677Spjd * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27129677Spjd * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28129677Spjd * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29129677Spjd * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30129677Spjd * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31129677Spjd * POSSIBILITY OF SUCH DAMAGE.
32129677Spjd */
33129677Spjd
34129677Spjd#include <sys/cdefs.h>
35129677Spjd__FBSDID("$FreeBSD: stable/10/lib/libutil/humanize_number.c 335891 2018-07-03 14:57:11Z robak $");
36129677Spjd
37129677Spjd#include <sys/types.h>
38129677Spjd#include <assert.h>
39176954Santoine#include <inttypes.h>
40129677Spjd#include <stdio.h>
41129677Spjd#include <stdlib.h>
42129677Spjd#include <string.h>
43129677Spjd#include <locale.h>
44129677Spjd#include <libutil.h>
45129677Spjd
46317387Sbrooksstatic const int maxscale = 6;
47220582Sdelphij
48129677Spjdint
49219939Sdelphijhumanize_number(char *buf, size_t len, int64_t quotient,
50129677Spjd    const char *suffix, int scale, int flags)
51129677Spjd{
52135792Spjd	const char *prefixes, *sep;
53220582Sdelphij	int	i, r, remainder, s1, s2, sign;
54256130Sjmg	int	divisordeccut;
55135792Spjd	int64_t	divisor, max;
56135792Spjd	size_t	baselen;
57129677Spjd
58256130Sjmg	/* Since so many callers don't check -1, NUL terminate the buffer */
59256130Sjmg	if (len > 0)
60256130Sjmg		buf[0] = '\0';
61129677Spjd
62256130Sjmg	/* validate args */
63256130Sjmg	if (buf == NULL || suffix == NULL)
64256130Sjmg		return (-1);
65256130Sjmg	if (scale < 0)
66256130Sjmg		return (-1);
67317387Sbrooks	else if (scale > maxscale &&
68256130Sjmg	    ((scale & ~(HN_AUTOSCALE|HN_GETSCALE)) != 0))
69256130Sjmg		return (-1);
70256130Sjmg	if ((flags & HN_DIVISOR_1000) && (flags & HN_IEC_PREFIXES))
71256130Sjmg		return (-1);
72256130Sjmg
73256130Sjmg	/* setup parameters */
74219939Sdelphij	remainder = 0;
75219939Sdelphij
76220582Sdelphij	if (flags & HN_IEC_PREFIXES) {
77220582Sdelphij		baselen = 2;
78129677Spjd		/*
79220582Sdelphij		 * Use the prefixes for power of two recommended by
80220582Sdelphij		 * the International Electrotechnical Commission
81220582Sdelphij		 * (IEC) in IEC 80000-3 (i.e. Ki, Mi, Gi...).
82220582Sdelphij		 *
83220582Sdelphij		 * HN_IEC_PREFIXES implies a divisor of 1024 here
84220582Sdelphij		 * (use of HN_DIVISOR_1000 would have triggered
85220582Sdelphij		 * an assertion earlier).
86129677Spjd		 */
87129677Spjd		divisor = 1024;
88256130Sjmg		divisordeccut = 973;	/* ceil(.95 * 1024) */
89135792Spjd		if (flags & HN_B)
90220582Sdelphij			prefixes = "B\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei";
91135792Spjd		else
92240391Sbapt			prefixes = "\0\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei";
93220582Sdelphij	} else {
94220582Sdelphij		baselen = 1;
95256130Sjmg		if (flags & HN_DIVISOR_1000) {
96220582Sdelphij			divisor = 1000;
97256130Sjmg			divisordeccut = 950;
98256130Sjmg			if (flags & HN_B)
99256130Sjmg				prefixes = "B\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E";
100256130Sjmg			else
101256130Sjmg				prefixes = "\0\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E";
102256130Sjmg		} else {
103220582Sdelphij			divisor = 1024;
104256130Sjmg			divisordeccut = 973;	/* ceil(.95 * 1024) */
105256130Sjmg			if (flags & HN_B)
106256130Sjmg				prefixes = "B\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E";
107256130Sjmg			else
108256130Sjmg				prefixes = "\0\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E";
109256130Sjmg		}
110129677Spjd	}
111129677Spjd
112220582Sdelphij#define	SCALE2PREFIX(scale)	(&prefixes[(scale) * 3])
113135792Spjd
114219939Sdelphij	if (quotient < 0) {
115129677Spjd		sign = -1;
116219939Sdelphij		quotient = -quotient;
117220582Sdelphij		baselen += 2;		/* sign, digit */
118129677Spjd	} else {
119129677Spjd		sign = 1;
120220582Sdelphij		baselen += 1;		/* digit */
121129677Spjd	}
122135792Spjd	if (flags & HN_NOSPACE)
123135792Spjd		sep = "";
124135792Spjd	else {
125135792Spjd		sep = " ";
126135792Spjd		baselen++;
127135792Spjd	}
128135792Spjd	baselen += strlen(suffix);
129129677Spjd
130135792Spjd	/* Check if enough room for `x y' + suffix + `\0' */
131135792Spjd	if (len < baselen + 1)
132129677Spjd		return (-1);
133129677Spjd
134135792Spjd	if (scale & (HN_AUTOSCALE | HN_GETSCALE)) {
135135792Spjd		/* See if there is additional columns can be used. */
136219939Sdelphij		for (max = 1, i = len - baselen; i-- > 0;)
137135792Spjd			max *= 10;
138129677Spjd
139176954Santoine		/*
140176954Santoine		 * Divide the number until it fits the given column.
141176954Santoine		 * If there will be an overflow by the rounding below,
142176954Santoine		 * divide once more.
143176954Santoine		 */
144219939Sdelphij		for (i = 0;
145256130Sjmg		    (quotient >= max || (quotient == max - 1 &&
146335891Srobak		    (remainder >= divisordeccut || remainder >=
147335891Srobak		    divisor / 2))) && i < maxscale; i++) {
148219939Sdelphij			remainder = quotient % divisor;
149219939Sdelphij			quotient /= divisor;
150219939Sdelphij		}
151129677Spjd
152135792Spjd		if (scale & HN_GETSCALE)
153135792Spjd			return (i);
154219939Sdelphij	} else {
155219939Sdelphij		for (i = 0; i < scale && i < maxscale; i++) {
156219939Sdelphij			remainder = quotient % divisor;
157219939Sdelphij			quotient /= divisor;
158219939Sdelphij		}
159219939Sdelphij	}
160129677Spjd
161135792Spjd	/* If a value <= 9.9 after rounding and ... */
162256130Sjmg	/*
163256130Sjmg	 * XXX - should we make sure there is enough space for the decimal
164256130Sjmg	 * place and if not, don't do HN_DECIMAL?
165256130Sjmg	 */
166256130Sjmg	if (((quotient == 9 && remainder < divisordeccut) || quotient < 9) &&
167256130Sjmg	    i > 0 && flags & HN_DECIMAL) {
168256130Sjmg		s1 = (int)quotient + ((remainder * 10 + divisor / 2) /
169256130Sjmg		    divisor / 10);
170256130Sjmg		s2 = ((remainder * 10 + divisor / 2) / divisor) % 10;
171135792Spjd		r = snprintf(buf, len, "%d%s%d%s%s%s",
172135792Spjd		    sign * s1, localeconv()->decimal_point, s2,
173135792Spjd		    sep, SCALE2PREFIX(i), suffix);
174129677Spjd	} else
175176954Santoine		r = snprintf(buf, len, "%" PRId64 "%s%s%s",
176256130Sjmg		    sign * (quotient + (remainder + divisor / 2) / divisor),
177135792Spjd		    sep, SCALE2PREFIX(i), suffix);
178129677Spjd
179129677Spjd	return (r);
180129677Spjd}
181