Skip to content

Commit

Permalink
Add the uppercase B conversion specifier to printf (#1300)
Browse files Browse the repository at this point in the history
The (uppercase) B conversion specifier is specified by the C standard to
have the same behavior as the (lowercase) b conversion specifier, except
that whenever the # flag is used, the (uppercase) B conversion specifier
alters a nonzero result by prefixing it with "0B", instead of with "0b".

This commit adds this conversion specifier alongside a few tests for it.
  • Loading branch information
GabrielRavier authored Sep 26, 2024
1 parent 518eaba commit 333c3d1
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 114 deletions.
3 changes: 2 additions & 1 deletion libc/stdio/fmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1090,9 +1090,10 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) {
case 'x':
log2base = 4;
goto FormatNumber;
case 'B':
case 'b':
log2base = 1;
alphabet = "0123456789abcdefpb";
alphabet = (d == 'b' ? "0123456789abcdefpb" : "0123456789ABCDEFPB");
goto FormatNumber;
case 'o':
log2base = 3;
Expand Down
206 changes: 93 additions & 113 deletions test/libc/stdio/snprintf_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,70 @@
#include "libc/str/str.h"
#include "libc/testlib/testlib.h"

static void check_single_double(const char *fmt, const char *expected_str,
double value) {
char buf[30] = {0};
int i = snprintf(buf, sizeof(buf), fmt, value);

ASSERT_GE(sizeof(buf), strlen(expected_str));
ASSERT_EQ(strlen(expected_str), i);
ASSERT_STREQ(expected_str, buf);
while (i < sizeof(buf))
ASSERT_EQ('\0', buf[i++]);
}

static void check_single_long_double(const char *fmt, const char *expected_str,
long double value) {
char buf[30] = {0};
int i = snprintf(buf, sizeof(buf), fmt, value);

ASSERT_GE(sizeof(buf), strlen(expected_str));
ASSERT_EQ(strlen(expected_str), i);
ASSERT_STREQ(expected_str, buf);
while (i < sizeof(buf))
ASSERT_EQ('\0', buf[i++]);
}

void check_single_long_double_arr_allowed(
const char *fmt, const char *allowed_strs[], long double value) {
char buf[30] = {0};
int res = snprintf(buf, sizeof(buf), fmt, value);

for (size_t i = 0; allowed_strs[i] != NULL; ++i)
if (strlen(allowed_strs[i]) == res && strcmp(allowed_strs[i], buf) == 0)
return;

printf("Failed to find matching str for %`'s, allowed strs:\n", buf);
for (size_t i = 0; allowed_strs[i] != NULL; ++i)
printf("- %`'s\n", allowed_strs[i]);
fflush(stdout);
ASSERT_EQ(false, true);
}

static void check_single_int(const char *fmt, const char *expected_str,
int value) {
char buf[30] = {0};
int i = snprintf(buf, sizeof(buf), fmt, value);

ASSERT_GE(sizeof(buf), strlen(expected_str));
ASSERT_EQ(strlen(expected_str), i);
ASSERT_STREQ(expected_str, buf);
while (i < sizeof(buf))
ASSERT_EQ('\0', buf[i++]);
}

static void check_single_wint_t(const char *fmt, const char *expected_str,
wint_t value) {
char buf[30] = {0};
int i = snprintf(buf, sizeof(buf), fmt, value);

ASSERT_GE(sizeof(buf), strlen(expected_str));
ASSERT_EQ(strlen(expected_str), i);
ASSERT_STREQ(expected_str, buf);
while (i < sizeof(buf))
ASSERT_EQ('\0', buf[i++]);
}

TEST(snprintf, testVeryLargePrecision) {
char buf[512] = {};
int i = snprintf(buf, sizeof(buf), "%.9999u", 10);
Expand All @@ -30,59 +94,21 @@ TEST(snprintf, testVeryLargePrecision) {
}

TEST(snprintf, testPlusFlagOnChar) {
char buf[10] = {};
int i = snprintf(buf, sizeof(buf), "%+c", '=');

ASSERT_EQ(1, i);
ASSERT_STREQ("=", buf);
check_single_int("%+c", "=", '=');
}

TEST(snprintf, testInf) {
char buf[10] = {};
int i = snprintf(buf, sizeof(buf), "%f", 1.0 / 0.0);

ASSERT_EQ(3, i);
ASSERT_STREQ("inf", buf);

memset(buf, '\0', 4);
i = snprintf(buf, sizeof(buf), "%Lf", 1.0L / 0.0L);
ASSERT_EQ(3, i);
ASSERT_STREQ("inf", buf);

memset(buf, '\0', 4);
i = snprintf(buf, sizeof(buf), "%e", 1.0 / 0.0);
ASSERT_EQ(3, i);
ASSERT_STREQ("inf", buf);

memset(buf, '\0', 4);
i = snprintf(buf, sizeof(buf), "%Le", 1.0L / 0.0L);
ASSERT_EQ(3, i);
ASSERT_STREQ("inf", buf);

memset(buf, '\0', 4);
i = snprintf(buf, sizeof(buf), "%g", 1.0 / 0.0);
ASSERT_EQ(3, i);
ASSERT_STREQ("inf", buf);

memset(buf, '\0', 4);
i = snprintf(buf, sizeof(buf), "%Lg", 1.0L / 0.0L);
ASSERT_EQ(3, i);
ASSERT_STREQ("inf", buf);

for (i = 4; i < 10; ++i)
ASSERT_EQ('\0', buf[i]);
check_single_double("%f", "inf", 1.0 / 0.0);
check_single_long_double("%Lf", "inf", 1.0L / 0.0L);
check_single_double("%e", "inf", 1.0 / 0.0);
check_single_long_double("%Le", "inf", 1.0L / 0.0L);
check_single_double("%g", "inf", 1.0 / 0.0);
check_single_long_double("%Lg", "inf", 1.0L / 0.0L);
}

TEST(snprintf, testUppercaseCConversionSpecifier) {
char buf[10] = {};
int i = snprintf(buf, sizeof(buf), "%C", L'a');

ASSERT_EQ(1, i);
ASSERT_STREQ("a", buf);

i = snprintf(buf, sizeof(buf), "%C", L'☺');
ASSERT_EQ(3, i);
ASSERT_STREQ("☺", buf);
check_single_wint_t("%C", "a", L'a');
check_single_wint_t("%C", "☺", L'☺');
}

// Make sure we don't va_arg the wrong argument size on wide character
Expand Down Expand Up @@ -188,74 +214,26 @@ TEST(snprintf, testNConversionSpecifier) {
}

TEST(snprintf, testLongDoubleEConversionSpecifier) {
char buf[20] = {};
int i = snprintf(buf, sizeof(buf), "%Le", 1234567.8L);

ASSERT_EQ(12, i);
ASSERT_STREQ("1.234568e+06", buf);
check_single_long_double("%Le", "1.234568e+06", 1234567.8L);
}

TEST(snprintf, testLongDoubleRounding) {
int previous_rounding = fegetround();
ASSERT_EQ(0, fesetround(FE_DOWNWARD));

char buf[20];
int i = snprintf(buf, sizeof(buf), "%.3Lf", 4.4375L);
ASSERT_EQ(5, i);
ASSERT_STREQ("4.437", buf);

i = snprintf(buf, sizeof(buf), "%.3Lf", -4.4375L);
ASSERT_EQ(6, i);
ASSERT_STREQ("-4.438", buf);
check_single_long_double("%.3Lf", "4.437", 4.4375L);
check_single_long_double("%.3Lf", "-4.438", -4.4375L);

ASSERT_EQ(0, fesetround(FE_TOWARDZERO));

i = snprintf(buf, sizeof(buf), "%.3Lf", -4.4375L);
ASSERT_EQ(6, i);
ASSERT_STREQ("-4.437", buf);
check_single_long_double("%.3Lf", "-4.437", -4.4375L);

ASSERT_EQ(0, fesetround(previous_rounding));
}

void check_a_conversion_specifier_double(const char *fmt,
const char *expected_str,
double value) {
char buf[30] = {0};
int i = snprintf(buf, sizeof(buf), fmt, value);

ASSERT_EQ(strlen(expected_str), i);
ASSERT_STREQ(expected_str, buf);
}

void check_a_conversion_specifier_long_double(const char *fmt,
const char *expected_str,
long double value) {
char buf[30] = {0};
int i = snprintf(buf, sizeof(buf), fmt, value);

ASSERT_EQ(strlen(expected_str), i);
ASSERT_STREQ(expected_str, buf);
}

void check_a_conversion_specifier_long_double_arr_allowed(
const char *fmt, const char *allowed_strs[], long double value) {
char buf[30] = {0};
int res = snprintf(buf, sizeof(buf), fmt, value);

for (size_t i = 0; allowed_strs[i] != NULL; ++i)
if (strlen(allowed_strs[i]) == res && strcmp(allowed_strs[i], buf) == 0)
return;

printf("Failed to find matching str for %`'s, allowed strs:\n", buf);
for (size_t i = 0; allowed_strs[i] != NULL; ++i)
printf("- %`'s\n", allowed_strs[i]);
fflush(stdout);
ASSERT_EQ(false, true);
}

void check_a_conversion_specifier_double_prec_1(const char *expected_str,
double value) {
check_a_conversion_specifier_double("%.1a", expected_str, value);
check_single_double("%.1a", expected_str, value);
}

TEST(snprintf, testAConversionSpecifierRounding) {
Expand All @@ -281,23 +259,25 @@ TEST(snprintf, testAConversionSpecifier) {
check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.98p+4);
check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.a8p+4);

check_a_conversion_specifier_double("%#a", "0x0.p+0", 0x0.0p0);
check_a_conversion_specifier_double("%#A", "0X0.P+0", 0x0.0p0);
check_a_conversion_specifier_long_double("%#La", "0x0.p+0", 0x0.0p0L);
check_a_conversion_specifier_long_double("%#LA", "0X0.P+0", 0x0.0p0L);

check_a_conversion_specifier_double("%.2a", "0x1.00p-1026", 0xf.fffp-1030);
check_single_double("%#a", "0x0.p+0", 0x0.0p0);
check_single_double("%#A", "0X0.P+0", 0x0.0p0);
check_single_long_double("%#La", "0x0.p+0", 0x0.0p0L);
check_single_long_double("%#LA", "0X0.P+0", 0x0.0p0L);

check_a_conversion_specifier_double("%.1a", "0x2.0p+0", 1.999);
check_single_double("%.2a", "0x1.00p-1026", 0xf.fffp-1030);

check_single_double("%.1a", "0x2.0p+0", 1.999);
const char *acceptable_results1[] = {"0x1.0p+1", "0x2.0p+0", NULL};
check_a_conversion_specifier_long_double_arr_allowed(
check_single_long_double_arr_allowed(
"%.1La", acceptable_results1, 1.999L);
}

TEST(snprintf, apostropheFlag) {
char buf[20];
int i = snprintf(buf, sizeof(buf), "%'d", 1000000);
ASSERT_EQ(7, i);
ASSERT_STREQ("1000000", buf);
TEST(snprintf, testApostropheFlag) {
check_single_int("%'d", "10000000", 10000000);
}

TEST(snprintf, testUppercaseBConversionSpecifier) {
check_single_int("%B", "0", 0);
check_single_int("%B", "10", 2);
check_single_int("%#B", "0B10011", 19);
}

0 comments on commit 333c3d1

Please sign in to comment.