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