Add formatting of bytes into the best unit of measurement

This commit is contained in:
Laytan Laats
2023-09-01 16:21:43 +02:00
parent 4aa4317c28
commit 735cfcd290
6 changed files with 130 additions and 0 deletions

View File

@@ -35,6 +35,8 @@ Floating-point, complex numbers, and quaternions:
%F synonym for %f
%h hexadecimal (lower-case) representation with 0h prefix (0h01234abcd)
%H hexadecimal (upper-case) representation with 0H prefix (0h01234ABCD)
%m number of bytes in the best unit of measurement, e.g. 123.45mb
%M number of bytes in the best unit of measurement, e.g. 123.45MB
String and slice of bytes
%s the uninterpreted bytes of the string or slice
%q a double-quoted string safely escaped with Odin syntax
@@ -85,6 +87,7 @@ Other flags:
add leading 0z for dozenal (%#z)
add leading 0x or 0X for hexadecimal (%#x or %#X)
remove leading 0x for %p (%#p)
add a space between bytes and the unit of measurement (%#m or %#M)
' ' (space) leave a space for elided sign in numbers (% d)
0 pad with leading zeros rather than spaces

View File

@@ -1048,6 +1048,65 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
fi.zero = false
_pad(fi, s)
}
// Units of measurements:
__MEMORY_LOWER := " b kb mb gb tb pb eb"
__MEMORY_UPPER := " B KB MB GB TB PB EB"
// Formats an integer value as bytes with the best representation.
//
// Inputs:
// - fi: A pointer to an Info structure
// - u: The integer value to format
// - is_signed: A boolean indicating if the integer is signed
// - bit_size: The bit size of the integer
// - digits: A string containing the digits for formatting
//
_fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: string) {
abs, neg := strconv.is_integer_negative(u, is_signed, bit_size)
// Default to a precision of 2, but if less than a kb, 0
prec := fi.prec if (fi.prec_set || abs < mem.Kilobyte) else 2
div, off, unit_len := 1, 0, 1
for n := abs; n >= mem.Kilobyte; n /= mem.Kilobyte {
div *= mem.Kilobyte
off += 3
// First iteration is slightly different because you go from
// units of length 1 to units of length 2.
if unit_len == 1 {
off = 2
unit_len = 2
}
}
// If hash, we add a space between the value and the suffix.
if fi.hash {
unit_len += 1
} else {
off += 1
}
amt := f64(abs) / f64(div)
if neg {
amt = -amt
}
buf: [256]byte
str := strconv.append_float(buf[:], amt, 'f', prec, 64)
// Add the unit at the end.
copy(buf[len(str):], units[off:off+unit_len])
str = string(buf[:len(str)+unit_len])
if !fi.plus {
// Strip sign from "+<value>" but not "+Inf".
if str[0] == '+' && str[1] != 'I' {
str = str[1:]
}
}
_pad(fi, str)
}
// Hex Values:
__DIGITS_LOWER := "0123456789abcdefx"
__DIGITS_UPPER := "0123456789ABCDEFX"
@@ -1096,6 +1155,8 @@ fmt_int :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, verb: rune) {
io.write_string(fi.writer, "U+", &fi.n)
_fmt_int(fi, u, 16, false, bit_size, __DIGITS_UPPER)
}
case 'm': _fmt_memory(fi, u, is_signed, bit_size, __MEMORY_LOWER)
case 'M': _fmt_memory(fi, u, is_signed, bit_size, __MEMORY_UPPER)
case:
fmt_bad_verb(fi, verb)

View File

@@ -8,6 +8,8 @@ Kilobyte :: runtime.Kilobyte
Megabyte :: runtime.Megabyte
Gigabyte :: runtime.Gigabyte
Terabyte :: runtime.Terabyte
Petabyte :: runtime.Petabyte
Exabyte :: runtime.Exabyte
set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr {
return runtime.memset(data, i32(value), len)

View File

@@ -337,6 +337,8 @@ Kilobyte :: 1024 * Byte
Megabyte :: 1024 * Kilobyte
Gigabyte :: 1024 * Megabyte
Terabyte :: 1024 * Gigabyte
Petabyte :: 1024 * Terabyte
Exabyte :: 1024 * Petabyte
// Logging stuff

View File

@@ -57,3 +57,6 @@ c_libc_test:
net_test:
$(ODIN) run net -out:test_core_net
fmt_test:
$(ODIN) run fmt -out:test_core_fmt

View File

@@ -0,0 +1,59 @@
package test_core_fmt
import "core:fmt"
import "core:os"
import "core:testing"
import "core:mem"
TEST_count := 0
TEST_fail := 0
when ODIN_TEST {
expect :: testing.expect
log :: testing.log
} else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
TEST_count += 1
if !condition {
TEST_fail += 1
fmt.printf("[%v] %v\n", loc, message)
return
}
}
log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc)
fmt.printf("log: %v\n", v)
}
}
main :: proc() {
t := testing.T{}
test_fmt_memory(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 {
os.exit(1)
}
}
test_fmt_memory :: proc(t: ^testing.T) {
check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) {
got := fmt.tprintf(format, ..args)
expect(t, got == exp, fmt.tprintf("(%q, %v): %q != %q", format, args, got, exp), loc)
}
check(t, "5b", "%m", 5)
check(t, "5B", "%M", 5)
check(t, "-5B", "%M", -5)
check(t, "3.00kb", "%m", mem.Kilobyte * 3)
check(t, "3kb", "%.0m", mem.Kilobyte * 3)
check(t, "3KB", "%.0M", mem.Kilobyte * 3)
check(t, "3.000 mb", "%#.3m", mem.Megabyte * 3)
check(t, "3.50 gb", "%#m", u32(mem.Gigabyte * 3.5))
check(t, "001tb", "%5.0m", mem.Terabyte)
check(t, "-001tb", "%5.0m", -mem.Terabyte)
check(t, "2.50 pb", "%#5.m", uint(mem.Petabyte * 2.5))
check(t, "1.00 EB", "%#M", mem.Exabyte)
check(t, "255 B", "%#M", u8(255))
check(t, "0b", "%m", u8(0))
}