Home | History | Annotate | Line # | Download | only in libutil
      1 /*	$NetBSD: strpct.c,v 1.7 2025/12/14 16:28:05 kre Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1998, 2025 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Erik E. Fair and Roland Illig.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /*
     33  * Calculate a percentage without resorting to floating point
     34  * and return a pointer to a string
     35  *
     36  * "digits" is the number of digits past the decimal place you want
     37  * (zero being the straight percentage with no decimals)
     38  *
     39  * Erik E. Fair <fair (at) clock.org>, May 8, 1997
     40  */
     41 
     42 #include <sys/cdefs.h>
     43 __RCSID("$NetBSD: strpct.c,v 1.7 2025/12/14 16:28:05 kre Exp $");
     44 
     45 #include <limits.h>
     46 #include <locale.h>
     47 #include <stdbool.h>
     48 #include <stdint.h>
     49 #include <stdio.h>
     50 #include <string.h>
     51 #include <util.h>
     52 
     53 static uintmax_t
     54 imax_abs(intmax_t x)
     55 {
     56 
     57 	return x < 0 ? -(uintmax_t)x : (uintmax_t)x;
     58 }
     59 
     60 static  uint32_t _round_ = STRPCT_RTZ;	/* Historic method */
     61 /*
     62  * Set the rounding mode used by the strpct() and strspct() functions
     63  */
     64 uint32_t
     65 strpct_round(uint32_t mode)
     66 {
     67 	uint32_t oldmode = _round_;
     68 
     69 	switch (mode) {
     70 	case STRPCT_RTN:
     71 	case STRPCT_RTZ:
     72 	case STRPCT_RAZ:
     73 	case STRPCT_RTI:
     74 	case STRPCT_RAI:
     75 		_round_ = mode;
     76 		break;
     77 	}
     78 
     79 	return oldmode;
     80 }
     81 
     82 char *
     83 strspct(char *buf, size_t bufsiz, intmax_t numerator, intmax_t denominator,
     84     size_t digits)
     85 {
     86 	return
     87 	    strspct_r(buf, bufsiz, numerator, denominator, digits, _round_);
     88 }
     89 
     90 char *
     91 strspct_r(char *buf, size_t bufsiz, intmax_t numerator, intmax_t denominator,
     92     size_t digits, uint32_t round)
     93 {
     94 	int sign;
     95 
     96 	if (bufsiz == 1)
     97 		buf[0] = '\0';
     98 	if (bufsiz <= 1)
     99 		return buf;
    100 
    101 	sign = (numerator < 0) != (denominator < 0);
    102 	if (sign && (round == STRPCT_RTI || round == STRPCT_RAI))
    103 		round ^= (STRPCT_RTI ^ STRPCT_RAI);
    104 
    105 	(void)strpct_r(buf + sign, bufsiz - sign, imax_abs(numerator),
    106 	    imax_abs(denominator), digits, round);
    107 	if (sign)
    108 		*buf = '-';
    109 	return buf;
    110 }
    111 
    112 typedef struct {
    113 	unsigned hi;
    114 	uintmax_t lo;
    115 } bignum;
    116 
    117 static bignum
    118 bignum_plus(bignum x, bignum y)
    119 {
    120 	x.hi += y.hi;
    121 	if (x.lo + y.lo < x.lo)
    122 		x.hi++;
    123 	x.lo += y.lo;
    124 	return x;
    125 }
    126 
    127 static bignum
    128 bignum_minus_u(bignum x, uintmax_t y)
    129 {
    130 	if (x.lo - y > x.lo)
    131 		x.hi--;
    132 	x.lo -= y;
    133 	return x;
    134 }
    135 
    136 static bignum
    137 bignum_times_10(bignum x)
    138 {
    139 	bignum x2 = bignum_plus(x, x);
    140 	bignum x4 = bignum_plus(x2, x2);
    141 	bignum x8 = bignum_plus(x4, x4);
    142 	return bignum_plus(x2, x8);
    143 }
    144 
    145 static bool
    146 bignum_ge_u(bignum x, uintmax_t y)
    147 {
    148 	return x.hi > 0 || x.lo >= y;
    149 }
    150 
    151 char *
    152 strpct(char *buf, size_t bufsiz, uintmax_t numerator, uintmax_t denominator,
    153     size_t digits)
    154 {
    155 	return
    156 	    strpct_r(buf, bufsiz, numerator, denominator, digits, _round_);
    157 }
    158 
    159 char *
    160 strpct_r(char *buf, size_t bufsiz, uintmax_t numerator, uintmax_t denominator,
    161     size_t digits, uint32_t round)
    162 {
    163 	char *p = buf;
    164 	size_t n = bufsiz;
    165 
    166 	if (denominator == 0)
    167 		denominator = 1;
    168 
    169 	if (numerator >= denominator) {
    170 		uintmax_t quotient = numerator / denominator;
    171 		uintmax_t remainder = numerator - (quotient * denominator);
    172 		size_t nw = snprintf(p, n, "%ju", quotient);
    173 
    174 		if (nw >= n) {
    175 			/*
    176 			 * We have only unsigned numbers here, so
    177 			 * rounding away from zero and towards Inf are the same
    178 			 * Sim rounding towards zero, and away from Inf
    179 			 * (away from Inf is the same thing as towards -Inf)
    180 			 */
    181 			switch (round & 3) {
    182 				/* round towards 0, round towards -Inf */
    183 			case STRPCT_RTZ:
    184 					/* truncate; do nothing */
    185 				break;
    186 
    187 				/* round to nearest (0.5 always away) */
    188 			case STRPCT_RTN:
    189 			default:
    190 				if (remainder * 2 < denominator)
    191 					break;
    192 				/* FALLTHROUGH */
    193 
    194 				/* round away from 0, or towards Inf */
    195 			case STRPCT_RAZ:
    196 				if (remainder == 0)
    197 					break;
    198 
    199 				(void)snprintf(p, n, "%ju", quotient + 1);
    200 				break;
    201 			}
    202 			return buf;
    203 		}
    204 		p += nw, n -= nw;
    205 		numerator = remainder;
    206 	}
    207 
    208 	if (n < 2) {
    209 		if (n != 0)
    210 			buf[0] = '\0';
    211 		return buf;
    212 	}
    213 
    214 	bignum num = { 0, numerator };
    215 	for (size_t i = 0; i < 2 + digits; i++) {
    216 		num = bignum_times_10(num);
    217 
    218 		unsigned digit = 0;
    219 		for (; bignum_ge_u(num, denominator); digit++)
    220 			num = bignum_minus_u(num, denominator);
    221 
    222 		if (i == 1 + digits || n <= 2) {
    223 			/*
    224 			 * This will be the last digit, so round it.
    225 			 * nb: all values here are non-negative
    226 			 */
    227 			if (!(round & 1)) {		      /* round down  */
    228 			    if (round != 0 ? bignum_ge_u(num, 1) :  /* up   */
    229 			        bignum_ge_u(bignum_plus(num, num),  /* rtn */
    230 							 denominator)) {
    231 				if (digit < 9) {
    232 				    digit++;
    233 				} else {
    234 				    if (n == 0)
    235 					break;
    236 				    digit = 0;
    237 				    if (p == buf) {
    238 					*p++ = '1';
    239 					n--;
    240 				    } else for (char *q = p; --q >= buf; ) {
    241 					if (*q < '0' || *q > '9') /* the '.' */
    242 						continue;
    243 					if (*q == '9') {
    244 					    *q = '0';
    245 					    if (q == buf) {
    246 						if ((size_t)(p - buf) >=
    247 						    bufsiz - 1)
    248 							p--;
    249 						memmove(q+1,q,(size_t)(p-buf));
    250 						*q = '1';
    251 						p++;
    252 						n--;
    253 						break;
    254 					    }
    255 					} else {
    256 					    (*q)++;
    257 					    break;
    258 					}
    259 				    }
    260 				}
    261 			    }
    262 			}
    263 		}
    264 
    265 		if (i > 0 || p > buf || digit > 0) {
    266 			if (n >= 2)
    267 				*p++ = '0' + digit, n--;
    268 			if (n >= 1)
    269 				*p = '\0';
    270 			if (n <= 1)
    271 				break;
    272 		}
    273 
    274 		if (i == 1 && digits > 0) {
    275 			const char *dp = localeconv()->decimal_point;
    276 			while (*dp != '\0' && n >= 2)
    277 				*p++ = *dp++, n--;
    278 			if (n >= 1)
    279 				*p = '\0';
    280 			if (n <= 1)
    281 				break;
    282 		}
    283 	}
    284 	return buf;
    285 }
    286