1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <pretty/sizes.h>
6 
7 #include <assert.h>
8 #include <stdbool.h>
9 #include <stdint.h>
10 #include <stdio.h>
11 
12 #include <zircon/assert.h>
13 
format_size_fixed(char * str,size_t str_size,size_t bytes,char unit)14 char* format_size_fixed(char* str, size_t str_size, size_t bytes, char unit) {
15     static const char units[] = "BkMGTPE";
16     static int num_units = sizeof(units) - 1;
17 
18     if (str_size == 0) {
19         // Even if NULL.
20         return str;
21     }
22     ZX_DEBUG_ASSERT(str != NULL);
23     if (str_size == 1) {
24         str[0] = '\0';
25         return str;
26     }
27 
28     char* orig_str = str;
29     size_t orig_bytes = bytes;
30 retry:;
31     int ui = 0;
32     uint16_t r = 0;
33     bool whole = true;
34     // If we have a fixed (non-zero) unit, divide until we hit it.
35     //
36     // Otherwise, divide until we reach a unit that can express the value
37     // with 4 or fewer whole digits.
38     // - If we can express the value without a fraction (it's a whole
39     //   kibi/mebi/gibibyte), use the largest possible unit (e.g., favor
40     //   "1M" over "1024k").
41     // - Otherwise, favor more whole digits to retain precision (e.g.,
42     //   favor "1025k" or "1025.0k" over "1.0M").
43     while (unit != 0
44                ? units[ui] != unit
45                : (bytes >= 10000 || (bytes != 0 && (bytes & 1023) == 0))) {
46         ui++;
47         if (ui >= num_units) {
48             // We probably got an unknown unit. Fall back to a natural unit,
49             // but leave a hint that something's wrong.
50             ZX_DEBUG_ASSERT(str_size > 1);
51             *str++ = '?';
52             str_size--;
53             unit = 0;
54             bytes = orig_bytes;
55             goto retry;
56         }
57         if (bytes & 1023) {
58             whole = false;
59         }
60         r = bytes % 1024;
61         bytes /= 1024;
62     }
63     if (whole) {
64         snprintf(str, str_size, "%zu%c", bytes, units[ui]);
65     } else {
66         // r represents the remainder of the most recent division operation.
67         // Since we provide a single unit of precision, we can round it based
68         // on the second digit and increment bytes in the case that it pushes
69         // the final value back over into a whole number.
70         unsigned int round_up = ((r % 100) >= 50);
71         r = (r / 100) + round_up;
72         if (r == 10) {
73             bytes++;
74             r = 0;
75         }
76         snprintf(str, str_size, "%zu.%1u%c", bytes, r, units[ui]);
77     }
78     return orig_str;
79 }
80 
format_size(char * str,size_t str_size,size_t bytes)81 char* format_size(char* str, size_t str_size, size_t bytes) {
82     return format_size_fixed(str, str_size, bytes, 0);
83 }
84