1 1.6 rillig /* $NetBSD: strpct.c,v 1.6 2025/05/13 04:50:45 rillig Exp $ */ 2 1.1 christos 3 1.1 christos /*- 4 1.5 rillig * Copyright (c) 1998, 2025 The NetBSD Foundation, Inc. 5 1.1 christos * All rights reserved. 6 1.1 christos * 7 1.1 christos * This code is derived from software contributed to The NetBSD Foundation 8 1.5 rillig * by Erik E. Fair and Roland Illig. 9 1.1 christos * 10 1.1 christos * Redistribution and use in source and binary forms, with or without 11 1.1 christos * modification, are permitted provided that the following conditions 12 1.1 christos * are met: 13 1.1 christos * 1. Redistributions of source code must retain the above copyright 14 1.1 christos * notice, this list of conditions and the following disclaimer. 15 1.1 christos * 2. Redistributions in binary form must reproduce the above copyright 16 1.1 christos * notice, this list of conditions and the following disclaimer in the 17 1.1 christos * documentation and/or other materials provided with the distribution. 18 1.1 christos * 19 1.1 christos * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 1.1 christos * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 1.1 christos * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 1.1 christos * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 1.1 christos * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 1.1 christos * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 1.1 christos * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.1 christos * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 1.1 christos * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 1.1 christos * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 1.1 christos * POSSIBILITY OF SUCH DAMAGE. 30 1.1 christos */ 31 1.1 christos 32 1.1 christos /* 33 1.1 christos * Calculate a percentage without resorting to floating point 34 1.1 christos * and return a pointer to a string 35 1.1 christos * 36 1.1 christos * "digits" is the number of digits past the decimal place you want 37 1.1 christos * (zero being the straight percentage with no decimals) 38 1.1 christos * 39 1.1 christos * Erik E. Fair <fair (at) clock.org>, May 8, 1997 40 1.1 christos */ 41 1.1 christos 42 1.1 christos #include <sys/cdefs.h> 43 1.6 rillig __RCSID("$NetBSD: strpct.c,v 1.6 2025/05/13 04:50:45 rillig Exp $"); 44 1.1 christos 45 1.5 rillig #include <limits.h> 46 1.5 rillig #include <locale.h> 47 1.5 rillig #include <stdbool.h> 48 1.1 christos #include <stdint.h> 49 1.1 christos #include <stdio.h> 50 1.1 christos #include <util.h> 51 1.1 christos 52 1.4 rillig static uintmax_t 53 1.4 rillig imax_abs(intmax_t x) 54 1.4 rillig { 55 1.4 rillig 56 1.4 rillig return x < 0 ? -(uintmax_t)x : (uintmax_t)x; 57 1.4 rillig } 58 1.4 rillig 59 1.1 christos char * 60 1.3 christos strspct(char *buf, size_t bufsiz, intmax_t numerator, intmax_t denominator, 61 1.3 christos size_t digits) 62 1.3 christos { 63 1.3 christos 64 1.5 rillig if (bufsiz == 1) 65 1.5 rillig buf[0] = '\0'; 66 1.5 rillig if (bufsiz <= 1) 67 1.3 christos return buf; 68 1.3 christos 69 1.5 rillig int sign = (numerator < 0) != (denominator < 0); 70 1.4 rillig (void)strpct(buf + sign, bufsiz - sign, imax_abs(numerator), 71 1.4 rillig imax_abs(denominator), digits); 72 1.3 christos if (sign) 73 1.3 christos *buf = '-'; 74 1.3 christos return buf; 75 1.3 christos } 76 1.3 christos 77 1.5 rillig typedef struct { 78 1.5 rillig unsigned hi; 79 1.5 rillig uintmax_t lo; 80 1.5 rillig } bignum; 81 1.5 rillig 82 1.5 rillig static bignum 83 1.5 rillig bignum_plus(bignum x, bignum y) 84 1.5 rillig { 85 1.5 rillig x.hi += y.hi; 86 1.5 rillig if (x.lo + y.lo < x.lo) 87 1.5 rillig x.hi++; 88 1.5 rillig x.lo += y.lo; 89 1.5 rillig return x; 90 1.5 rillig } 91 1.5 rillig 92 1.5 rillig static bignum 93 1.5 rillig bignum_minus_u(bignum x, uintmax_t y) 94 1.5 rillig { 95 1.5 rillig if (x.lo - y > x.lo) 96 1.5 rillig x.hi--; 97 1.5 rillig x.lo -= y; 98 1.5 rillig return x; 99 1.5 rillig } 100 1.5 rillig 101 1.5 rillig static bignum 102 1.5 rillig bignum_times_10(bignum x) 103 1.5 rillig { 104 1.5 rillig bignum x2 = bignum_plus(x, x); 105 1.5 rillig bignum x4 = bignum_plus(x2, x2); 106 1.5 rillig bignum x8 = bignum_plus(x4, x4); 107 1.5 rillig return bignum_plus(x2, x8); 108 1.5 rillig } 109 1.5 rillig 110 1.5 rillig static bool 111 1.5 rillig bignum_ge_u(bignum x, uintmax_t y) 112 1.5 rillig { 113 1.5 rillig return x.hi > 0 || x.lo >= y; 114 1.5 rillig } 115 1.5 rillig 116 1.3 christos char * 117 1.1 christos strpct(char *buf, size_t bufsiz, uintmax_t numerator, uintmax_t denominator, 118 1.1 christos size_t digits) 119 1.1 christos { 120 1.5 rillig char *p = buf; 121 1.5 rillig size_t n = bufsiz; 122 1.1 christos 123 1.1 christos if (denominator == 0) 124 1.1 christos denominator = 1; 125 1.1 christos 126 1.5 rillig if (numerator >= denominator) { 127 1.5 rillig size_t nw = snprintf(p, n, "%ju", numerator / denominator); 128 1.5 rillig if (nw >= n) 129 1.5 rillig return buf; 130 1.5 rillig p += nw, n -= nw; 131 1.5 rillig numerator %= denominator; 132 1.5 rillig } 133 1.1 christos 134 1.5 rillig bignum num = { 0, numerator }; 135 1.5 rillig for (size_t i = 0; i < 2 + digits; i++) { 136 1.5 rillig num = bignum_times_10(num); 137 1.5 rillig 138 1.5 rillig unsigned digit = 0; 139 1.5 rillig for (; bignum_ge_u(num, denominator); digit++) 140 1.5 rillig num = bignum_minus_u(num, denominator); 141 1.5 rillig 142 1.5 rillig if (i > 0 || p > buf || digit > 0) { 143 1.6 rillig if (n >= 2) 144 1.6 rillig *p++ = '0' + digit, n--; 145 1.6 rillig if (n >= 1) 146 1.6 rillig *p = '\0'; 147 1.6 rillig if (n <= 1) 148 1.5 rillig break; 149 1.5 rillig } 150 1.5 rillig 151 1.5 rillig if (i == 1 && digits > 0) { 152 1.6 rillig const char *dp = localeconv()->decimal_point; 153 1.6 rillig while (*dp != '\0' && n >= 2) 154 1.6 rillig *p++ = *dp++, n--; 155 1.6 rillig if (n >= 1) 156 1.6 rillig *p = '\0'; 157 1.6 rillig if (n <= 1) 158 1.5 rillig break; 159 1.5 rillig } 160 1.5 rillig } 161 1.1 christos return buf; 162 1.1 christos } 163