Messages in this thread Patch in this message | | | Date | Wed, 24 Jan 2024 18:58:45 +0000 | From | Matthew Wilcox <> | Subject | [RFC] Printing numbers in SI units |
| |
I was looking at hugetlbfs and it has several snippets of code like this:
string_get_size(huge_page_size(h), 1, STRING_UNITS_2, buf, 32); pr_warn("HugeTLB: allocating %u of page size %s failed node%d. Only allocated %lu hugepages.\n", h->max_huge_pages_node[nid], buf, nid, i);
That's not terribly ergonomic, so I wondered if I could do better. Unfortunately, I decided to do it using the SPECIAL flag which GCC warns about. But I've written the code now, so I'm sending it out in case anybody has a better idea for how to incorporate it.
diff --git a/lib/test_printf.c b/lib/test_printf.c index 69b6a5e177f2..69af4d24a814 100644 --- a/lib/test_printf.c +++ b/lib/test_printf.c @@ -178,6 +178,16 @@ test_number(void) * behaviour. */ test("00|0|0|0|0", "%.2d|%.1d|%.0d|%.*d|%1.0d", 0, 0, 0, 0, 0, 0); + + /* + * C23 does not define the effect of "alternative form". Indeed + * I think it actually defines it to be Undefined Behaviour which + * apparently lets the compiler delete your entire source code. + */ + test("2KiB", "%#d", 2048); + test("2MiB", "%#d", 2048 * 1024); + test("1GiB", "%#d", 1024 * 1024 * 1024); + test("1000MiB", "%#d", 1024 * 1024 * 1000); } static void __init diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 552738f14275..a702582c598c 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -370,6 +370,56 @@ char *put_dec(char *buf, unsigned long long n) #endif +/* + * precision is the number of digits after the decimal place. we limit + * it to two, because more would be complicated and unnecessary. + */ +static noinline_for_stack +char *put_si(char *buf, unsigned long long n, int precision) +{ + char *name = "KMGTPEZB"; + int unit = 0; + unsigned remainder = 0; + + if (precision > 2) + precision = 2; + + while (n >= 1024) { + remainder = n % 1024; + n /= 1024; + unit++; + } + + remainder *= 1000; + remainder /= 1024; + + /* Round up */ + if (precision == 2) + remainder += 500; + else if (precision == 1) + remainder += 50; + else + remainder += 5; + if (remainder >= 1000) { + remainder -= 1000; + n += 1; + } + + *buf++ = 'B'; + if (unit > 0) { + *buf++ = 'i'; + *buf++ = name[unit - 1]; + } + + if (precision > 0) { + *((u16 *)buf) = decpair[remainder / 10]; + buf += precision; + *buf++ = '.'; + } + + return put_dec_trunc8(buf, n); +} + /* * Convert passed number to decimal string. * Returns the length of string. On buffer overflow, returns 0. @@ -507,6 +557,9 @@ char *number(char *buf, char *end, unsigned long long num, tmp[i++] = (hex_asc_upper[((unsigned char)num) & mask] | locase); num >>= shift; } while (num); + } else if (spec.flags & SPECIAL) { + i = put_si(tmp, num, precision) - tmp; + precision = i; } else { /* base 10 */ i = put_dec(tmp, num) - tmp; }
| |