Add the uppercase B conversion specifier to printf (#1300)

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.
This commit is contained in:
Gabriel Ravier 2024-09-26 13:27:45 +02:00 committed by GitHub
parent 518eabadf5
commit 333c3d1f0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 95 additions and 114 deletions

View file

@ -1090,9 +1090,10 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) {
case 'x': case 'x':
log2base = 4; log2base = 4;
goto FormatNumber; goto FormatNumber;
case 'B':
case 'b': case 'b':
log2base = 1; log2base = 1;
alphabet = "0123456789abcdefpb"; alphabet = (d == 'b' ? "0123456789abcdefpb" : "0123456789ABCDEFPB");
goto FormatNumber; goto FormatNumber;
case 'o': case 'o':
log2base = 3; log2base = 3;

View file

@ -21,6 +21,70 @@
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/testlib/testlib.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) { TEST(snprintf, testVeryLargePrecision) {
char buf[512] = {}; char buf[512] = {};
int i = snprintf(buf, sizeof(buf), "%.9999u", 10); int i = snprintf(buf, sizeof(buf), "%.9999u", 10);
@ -30,59 +94,21 @@ TEST(snprintf, testVeryLargePrecision) {
} }
TEST(snprintf, testPlusFlagOnChar) { TEST(snprintf, testPlusFlagOnChar) {
char buf[10] = {}; check_single_int("%+c", "=", '=');
int i = snprintf(buf, sizeof(buf), "%+c", '=');
ASSERT_EQ(1, i);
ASSERT_STREQ("=", buf);
} }
TEST(snprintf, testInf) { TEST(snprintf, testInf) {
char buf[10] = {}; check_single_double("%f", "inf", 1.0 / 0.0);
int i = snprintf(buf, sizeof(buf), "%f", 1.0 / 0.0); check_single_long_double("%Lf", "inf", 1.0L / 0.0L);
check_single_double("%e", "inf", 1.0 / 0.0);
ASSERT_EQ(3, i); check_single_long_double("%Le", "inf", 1.0L / 0.0L);
ASSERT_STREQ("inf", buf); check_single_double("%g", "inf", 1.0 / 0.0);
check_single_long_double("%Lg", "inf", 1.0L / 0.0L);
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]);
} }
TEST(snprintf, testUppercaseCConversionSpecifier) { TEST(snprintf, testUppercaseCConversionSpecifier) {
char buf[10] = {}; check_single_wint_t("%C", "a", L'a');
int i = snprintf(buf, sizeof(buf), "%C", L'a'); check_single_wint_t("%C", "", L'');
ASSERT_EQ(1, i);
ASSERT_STREQ("a", buf);
i = snprintf(buf, sizeof(buf), "%C", L'');
ASSERT_EQ(3, i);
ASSERT_STREQ("", buf);
} }
// Make sure we don't va_arg the wrong argument size on wide character // Make sure we don't va_arg the wrong argument size on wide character
@ -188,74 +214,26 @@ TEST(snprintf, testNConversionSpecifier) {
} }
TEST(snprintf, testLongDoubleEConversionSpecifier) { TEST(snprintf, testLongDoubleEConversionSpecifier) {
char buf[20] = {}; check_single_long_double("%Le", "1.234568e+06", 1234567.8L);
int i = snprintf(buf, sizeof(buf), "%Le", 1234567.8L);
ASSERT_EQ(12, i);
ASSERT_STREQ("1.234568e+06", buf);
} }
TEST(snprintf, testLongDoubleRounding) { TEST(snprintf, testLongDoubleRounding) {
int previous_rounding = fegetround(); int previous_rounding = fegetround();
ASSERT_EQ(0, fesetround(FE_DOWNWARD)); ASSERT_EQ(0, fesetround(FE_DOWNWARD));
char buf[20]; check_single_long_double("%.3Lf", "4.437", 4.4375L);
int i = snprintf(buf, sizeof(buf), "%.3Lf", 4.4375L); check_single_long_double("%.3Lf", "-4.438", -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);
ASSERT_EQ(0, fesetround(FE_TOWARDZERO)); ASSERT_EQ(0, fesetround(FE_TOWARDZERO));
i = snprintf(buf, sizeof(buf), "%.3Lf", -4.4375L); check_single_long_double("%.3Lf", "-4.437", -4.4375L);
ASSERT_EQ(6, i);
ASSERT_STREQ("-4.437", buf);
ASSERT_EQ(0, fesetround(previous_rounding)); 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, void check_a_conversion_specifier_double_prec_1(const char *expected_str,
double value) { double value) {
check_a_conversion_specifier_double("%.1a", expected_str, value); check_single_double("%.1a", expected_str, value);
} }
TEST(snprintf, testAConversionSpecifierRounding) { TEST(snprintf, testAConversionSpecifierRounding) {
@ -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.98p+4);
check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.a8p+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_single_double("%#a", "0x0.p+0", 0x0.0p0);
check_a_conversion_specifier_double("%#A", "0X0.P+0", 0x0.0p0); check_single_double("%#A", "0X0.P+0", 0x0.0p0);
check_a_conversion_specifier_long_double("%#La", "0x0.p+0", 0x0.0p0L); check_single_long_double("%#La", "0x0.p+0", 0x0.0p0L);
check_a_conversion_specifier_long_double("%#LA", "0X0.P+0", 0x0.0p0L); check_single_long_double("%#LA", "0X0.P+0", 0x0.0p0L);
check_a_conversion_specifier_double("%.2a", "0x1.00p-1026", 0xf.fffp-1030); check_single_double("%.2a", "0x1.00p-1026", 0xf.fffp-1030);
check_a_conversion_specifier_double("%.1a", "0x2.0p+0", 1.999);
check_single_double("%.1a", "0x2.0p+0", 1.999);
const char *acceptable_results1[] = {"0x1.0p+1", "0x2.0p+0", NULL}; 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); "%.1La", acceptable_results1, 1.999L);
} }
TEST(snprintf, apostropheFlag) { TEST(snprintf, testApostropheFlag) {
char buf[20]; check_single_int("%'d", "10000000", 10000000);
int i = snprintf(buf, sizeof(buf), "%'d", 1000000); }
ASSERT_EQ(7, i);
ASSERT_STREQ("1000000", buf); TEST(snprintf, testUppercaseBConversionSpecifier) {
check_single_int("%B", "0", 0);
check_single_int("%B", "10", 2);
check_single_int("%#B", "0B10011", 19);
} }