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
14char* 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;
30retry:;
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
81char* format_size(char* str, size_t str_size, size_t bytes) {
82    return format_size_fixed(str, str_size, bytes, 0);
83}
84