Last active
December 18, 2021 16:41
-
-
Save pkkm/629a66d47ecd16aa89e8b67ba5abd77d to your computer and use it in GitHub Desktop.
Criterion-like formatting of numbers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _POSIX_C_SOURCE 200809L // For strdup. | |
#include <stdio.h> | |
#include <assert.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <stdbool.h> | |
#include <math.h> // Remember to link with -lm. | |
#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0])) | |
typedef struct { | |
int exponent; | |
const char *symbol; | |
const char *bin_symbol; | |
} Prefix; | |
Prefix PREFIXES[] = { | |
{8, "Y", "Yi"}, | |
{7, "Z", "Zi"}, | |
{6, "E", "Ei"}, | |
{5, "P", "Pi"}, | |
{4, "T", "Ti"}, | |
{3, "G", "Gi"}, | |
{2, "M", "Mi"}, | |
{1, "k", "Ki"}, | |
{0, "", ""}, | |
{-1, "m", NULL}, | |
{-2, "μ", NULL}, | |
{-3, "n", NULL}, | |
{-4, "p", NULL}, | |
{-5, "f", NULL}, | |
{-6, "a", NULL}, | |
{-7, "z", NULL}, | |
{-8, "y", NULL} | |
}; | |
char *format_num_with_prefix(double num, const char *prefix) { | |
// Format to 4 digits, even if they are trailing zeros. | |
double num_abs = fabs(num); | |
char buffer[128]; | |
if (num_abs >= 1e9) { | |
snprintf(buffer, ARRAY_LEN(buffer), "%.4g %s", num, prefix); | |
return strdup(buffer); | |
} | |
for (int exponent = 3; exponent >= 1; exponent--) { | |
if (num_abs >= pow(10, exponent)) { | |
snprintf(buffer, ARRAY_LEN(buffer), "%.*f %s", 3 - exponent, num, prefix); | |
return strdup(buffer); | |
} | |
} | |
snprintf(buffer, ARRAY_LEN(buffer), "%.3f %s", num, prefix); | |
return strdup(buffer); | |
} | |
char *format_num(double num, bool binary) { | |
// Format a number with a SI prefix. | |
// Based on the Criterion library for Haskell. | |
// Alternative: <http://jkorpela.fi/c/eng.html>. | |
assert(isfinite(num)); | |
double num_abs = fabs(num); | |
double base = binary ? 1024 : 1000; | |
for (size_t i_prefix = 0; i_prefix < ARRAY_LEN(PREFIXES); i_prefix++) { | |
Prefix prefix = PREFIXES[i_prefix]; | |
const char *symbol = binary ? prefix.bin_symbol : prefix.symbol; | |
if (symbol == NULL) { | |
continue; | |
} | |
if (num_abs >= pow(base, prefix.exponent)) { | |
return format_num_with_prefix(num * pow(base, -prefix.exponent), symbol); | |
} | |
} | |
char buffer[128]; | |
snprintf(buffer, ARRAY_LEN(buffer), "%g ", num); | |
return strdup(buffer); | |
} | |
int main(int argc, char **argv) { | |
double example_time = 0.000005235724; | |
char *example_time_str = format_num(example_time, false); | |
printf("Example time: %ss\n", example_time_str); | |
free(example_time_str); | |
int example_size = 52 * 1024 * 1024; | |
char *example_size_str = format_num(example_size, true); | |
printf("Example file size: %sB\n", example_size_str); | |
free(example_size_str); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment