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