From 20eaae67d633356ab7e2baa594cb42565b660c26 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 6 May 2021 14:26:01 +0100 Subject: [PATCH] Add hex float printf support to utf8printf --- renderdoc/strings/utf8printf.cpp | 392 ++++++++++++++++++++++++++++--- 1 file changed, 357 insertions(+), 35 deletions(-) diff --git a/renderdoc/strings/utf8printf.cpp b/renderdoc/strings/utf8printf.cpp index 7df4011cd..b14d7875c 100644 --- a/renderdoc/strings/utf8printf.cpp +++ b/renderdoc/strings/utf8printf.cpp @@ -433,12 +433,17 @@ void PrintInteger(bool typeUnsigned, uint64_t argu, int base, uint64_t numbits, } } -void PrintFloat0(bool e, bool f, FormatterParams formatter, char prepend, char *&output, - size_t &actualsize, char *end) +void PrintFloat0(bool e, bool f, bool a, bool uppercaseDigits, FormatterParams formatter, + char prepend, char *&output, size_t &actualsize, char *end) { int numwidth = 0; - if(e) + if(a && formatter.Precision == FormatterParams::NoPrecision) + formatter.Precision = 0; + + if(a) + numwidth = formatter.Precision + 1 + 5; // 0 plus precision plus 0xp+0 + else if(e) numwidth = formatter.Precision + 1 + 4; // 0 plus precision plus e+00 else if(f || formatter.Flags & AlternateForm) numwidth = formatter.Precision + 1; // 0 plus precision @@ -446,10 +451,10 @@ void PrintFloat0(bool e, bool f, FormatterParams formatter, char prepend, char * numwidth = 1; // alternate form means . is included even if no digits after . - if(((e || f) && formatter.Precision > 0) || (formatter.Flags & AlternateForm)) + if(((e || f || a) && formatter.Precision > 0) || (formatter.Flags & AlternateForm)) numwidth++; // . - if(!e && !f && (formatter.Flags & AlwaysDecimal)) + if(!e && !f && !a && (formatter.Flags & AlwaysDecimal)) { numwidth += 2; // .0 } @@ -465,6 +470,9 @@ void PrintFloat0(bool e, bool f, FormatterParams formatter, char prepend, char * if(formatter.Flags & PadZeroes) { + if(a) + appendstring(output, actualsize, end, uppercaseDigits ? "0X" : "0x"); + if(prepend) addchar(output, actualsize, end, prepend); addchars(output, actualsize, end, size_t(padlen), '0'); @@ -474,11 +482,17 @@ void PrintFloat0(bool e, bool f, FormatterParams formatter, char prepend, char * addchars(output, actualsize, end, size_t(padlen), ' '); if(prepend) addchar(output, actualsize, end, prepend); + + if(a) + appendstring(output, actualsize, end, uppercaseDigits ? "0X" : "0x"); } else { if(prepend) addchar(output, actualsize, end, prepend); + + if(a) + appendstring(output, actualsize, end, uppercaseDigits ? "0X" : "0x"); } // print a .0 for all cases except non-alternate %g @@ -492,6 +506,18 @@ void PrintFloat0(bool e, bool f, FormatterParams formatter, char prepend, char * if(e) appendstring(output, actualsize, end, "e+00"); } + else if(a) + { + if(formatter.Precision == 0) + { + addchar(output, actualsize, end, '0'); + } + else + { + appendstring(output, actualsize, end, "0."); + addchars(output, actualsize, end, size_t(formatter.Precision), '0'); + } + } else { addchar(output, actualsize, end, '0'); @@ -503,13 +529,16 @@ void PrintFloat0(bool e, bool f, FormatterParams formatter, char prepend, char * } } + if(a) + appendstring(output, actualsize, end, "p+0"); + if(padlen > 0 && (formatter.Flags & LeftJustify)) { addchars(output, actualsize, end, size_t(padlen), ' '); } } -void PrintFloat(double argd, FormatterParams &formatter, bool e, bool f, bool g, +void PrintFloat(double argd, FormatterParams &formatter, bool e, bool f, bool g, bool a, bool uppercaseDigits, char *&output, size_t &actualsize, char *end) { // extract the pieces out of the double @@ -531,7 +560,7 @@ void PrintFloat(double argd, FormatterParams &formatter, bool e, bool f, bool g, // special-case handling of printing 0 if(rawexp == 0 && mantissa == 0) { - PrintFloat0(e, f, formatter, prepend, output, actualsize, end); + PrintFloat0(e, f, a, uppercaseDigits, formatter, prepend, output, actualsize, end); } // handle 'special' values, inf and nan else if(rawexp == 0x7ff) @@ -548,6 +577,198 @@ void PrintFloat(double argd, FormatterParams &formatter, bool e, bool f, bool g, appendstring(output, actualsize, end, uppercaseDigits ? "NAN" : "nan"); } } + else if(a) + { + char digits[18] = {0}; + int ndigits = 0; + + for(int d = 12; mantissa && d >= 0; d--) + { + const uint64_t mask = 0xfULL << (d * 4); + const uint64_t digit = (mantissa & mask) >> (d * 4); + + char c = char('0' + digit); + if(digit >= 10) + c = char((uppercaseDigits ? 'A' : 'a') + digit - 10); + + digits[ndigits++] = c; + + mantissa &= ~mask; + } + + // if no precision is specified, drop trailing 0s and ensure we don't pad or trim + if(formatter.Precision == FormatterParams::NoPrecision) + { + while(ndigits > 0 && digits[ndigits - 1] == '0') + ndigits--; + + formatter.Precision = ndigits; + } + + // hold the carry bit because if it's needed we have no decimals and need to format 0x2p+E + // instead of 0x1.Mp+E + bool carry = false; + + // see if we need to trim and round + if(ndigits > formatter.Precision) + { + int removedigs = ndigits - formatter.Precision; + + // if we're removing all digits, just check the first to see if it should be + // rounded up or down + if(removedigs == ndigits) + { + ndigits = 0; + // all hex digits are above '8' in ascii order, so we can do just one comparison + carry = (digits[0] >= '8'); + } + else + { + // remove the specified number of digits + ndigits -= removedigs; + + // round up the last digit (continually rolling up if necessary) + // note this will look 'ahead' into the last removed digits at first + carry = true; + for(int i = ndigits - 1; i >= 0; i--) + { + // should we round up? + if(digits[i + 1] >= '8') + { + digits[i + 1] = 0; + + // unless current digit is an f, we can just increment it and stop + if(digits[i] != 'f' && digits[i] != 'F') + { + if(digits[i] != '9') + digits[i]++; + else + digits[i] = uppercaseDigits ? 'A' : 'a'; + carry = false; + break; + } + + // continue (carry to next digit) + } + else + { + // didn't need to round up, everything's fine. + carry = false; + break; + } + + // trim off a digit (was an f/F) + ndigits--; + continue; + } + } + } + + if(digits[0] == '0' && !carry && ndigits == 0) + { + // if we rounded off to a 0.0, print it with special handling + PrintFloat0(e, f, a, uppercaseDigits, formatter, prepend, output, actualsize, end); + } + + int padtrailing0s = formatter.Precision - ndigits; + + { + int numwidth = 0; + + // first calculate the width of the produced output, so we can calculate any padding + + numwidth = 3; // 0x1 + numwidth += ndigits; // post-decimal digits + if(ndigits > 0 || (formatter.Flags & AlternateForm) || padtrailing0s > 0) + numwidth++; // '.' + numwidth += padtrailing0s; + numwidth += 2; // 'p+' or 'p-' + if(exponent >= 0xff || exponent <= -0xff) + numwidth += 3; + else if(exponent >= 0xf || exponent <= -0xf) + numwidth += 2; + else + numwidth += 1; + if(prepend) + numwidth++; // +, - or ' ' + + int padlen = 0; + + if(formatter.Width != FormatterParams::NoWidth && formatter.Width > numwidth) + padlen = formatter.Width - numwidth; + + // pad with 0s or ' 's and insert the sign character + if(formatter.Flags & PadZeroes) + { + if(a) + appendstring(output, actualsize, end, uppercaseDigits ? "0X" : "0x"); + + if(prepend) + addchar(output, actualsize, end, prepend); + addchars(output, actualsize, end, size_t(padlen), '0'); + } + else if(padlen > 0 && (formatter.Flags & LeftJustify) == 0) + { + addchars(output, actualsize, end, size_t(padlen), ' '); + if(prepend) + addchar(output, actualsize, end, prepend); + + if(a) + appendstring(output, actualsize, end, uppercaseDigits ? "0X" : "0x"); + } + else + { + if(prepend) + addchar(output, actualsize, end, prepend); + + if(a) + appendstring(output, actualsize, end, uppercaseDigits ? "0X" : "0x"); + } + + if(carry) + addchar(output, actualsize, end, '2'); + else + addchar(output, actualsize, end, '1'); + + // insert the decimals + if(ndigits > 0 || (formatter.Flags & AlternateForm) || padtrailing0s > 0) + addchar(output, actualsize, end, '.'); + for(int i = 0; i < ndigits; i++) + addchar(output, actualsize, end, digits[i]); + + // add the trailing 0s here + if(padtrailing0s > 0) + addchars(output, actualsize, end, size_t(padtrailing0s), '0'); + + // print the p+XXX exponential + addchar(output, actualsize, end, uppercaseDigits ? 'P' : 'p'); + if(exponent >= 0) + addchar(output, actualsize, end, '+'); + else + addchar(output, actualsize, end, '-'); + + int exponaccum = exponent >= 0 ? exponent : -exponent; + + if(exponaccum >= 1000) + addchar(output, actualsize, end, '0' + char(exponaccum / 1000)); + exponaccum %= 1000; + + if(exponaccum >= 100) + addchar(output, actualsize, end, '0' + char(exponaccum / 100)); + exponaccum %= 100; + + if(exponaccum >= 10) + addchar(output, actualsize, end, '0' + char(exponaccum / 10)); + exponaccum %= 10; + + addchar(output, actualsize, end, '0' + char(exponaccum)); + + if(padlen > 0 && (formatter.Flags & LeftJustify)) + { + addchars(output, actualsize, end, size_t(padlen), ' '); + } + } + } else { // call out to grisu2 to generate digits + exponent @@ -763,7 +984,7 @@ void PrintFloat(double argd, FormatterParams &formatter, bool e, bool f, bool g, else if(digits[0] == '0' && ndigits == 1) { // if we rounded off to a 0.0, print it with special handling - PrintFloat0(e, f, formatter, prepend, output, actualsize, end); + PrintFloat0(e, f, a, uppercaseDigits, formatter, prepend, output, actualsize, end); } else { @@ -1151,19 +1372,25 @@ void formatargument(char type, void *rawarg, FormatterParams formatter, char *&o PrintInteger(typeUnsigned, argu, base, numbits, formatter, uppercaseDigits, output, actualsize, end); } - else if(type == 'e' || type == 'E' || type == 'f' || type == 'F' || type == 'g' || type == 'G' - //|| type == 'a' || type == 'A' // hex floats not supported - ) + else if(type == 'e' || type == 'E' || type == 'f' || type == 'F' || type == 'g' || type == 'G' || + type == 'a' || type == 'A') { bool uppercaseDigits = type < 'a'; double argd = *(double *)rawarg; - if(formatter.Precision == FormatterParams::NoPrecision) + bool e = (type == 'e' || type == 'E'); + bool f = (type == 'f' || type == 'F'); + bool g = (type == 'g' || type == 'G'); + bool a = (type == 'a' || type == 'A'); + + if(!a && formatter.Precision == FormatterParams::NoPrecision) + { formatter.Precision = 6; - formatter.Precision = RDCMAX(0, formatter.Precision); + formatter.Precision = RDCMAX(0, formatter.Precision); + } - if(formatter.Precision == 0) + if(!a && formatter.Precision == 0) { if(argd > 0.0f && argd < 1.0f) argd = argd < 0.5f ? 0.0f : 1.0f; @@ -1171,11 +1398,7 @@ void formatargument(char type, void *rawarg, FormatterParams formatter, char *&o argd = argd > -0.5f ? 0.0f : -1.0f; } - bool e = (type == 'e' || type == 'E'); - bool f = (type == 'f' || type == 'F'); - bool g = (type == 'g' || type == 'G'); - - PrintFloat(argd, formatter, e, f, g, uppercaseDigits, output, actualsize, end); + PrintFloat(argd, formatter, e, f, g, a, uppercaseDigits, output, actualsize, end); } else { @@ -1295,25 +1518,33 @@ int utf8printv(char *buf, size_t bufsize, const char *fmt, va_list args) { iter++; - // invalid character following '.' it should be an integer // note standard printf supports * here to read precision from a vararg - if(*iter < '0' || *iter > '9') - RDCDUMPMSG("Unexpected character expecting precision"); - - formatter.Precision = int(*iter - '0'); - iter++; // step to next character - - // continue while encountering digits, accumulating into width - while(*iter >= '0' && *iter <= '9') + if(*iter == '*') { - formatter.Precision *= 10; - formatter.Precision += int(*iter - '0'); - iter++; + RDCDUMPMSG("Unexpected character * expecting precision"); } + // if there's no value for the precision, it is 0 + else if(*iter < '0' || *iter > '9') + { + formatter.Precision = 0; + } + else + { + formatter.Precision = int(*iter - '0'); + iter++; // step to next character - // unterminated formatter - if(*iter == 0) - RDCDUMPMSG("Unterminated % formatter found after precision"); + // continue while encountering digits, accumulating into width + while(*iter >= '0' && *iter <= '9') + { + formatter.Precision *= 10; + formatter.Precision += int(*iter - '0'); + iter++; + } + + // unterminated formatter + if(*iter == 0) + RDCDUMPMSG("Unterminated % formatter found after precision"); + } } else { @@ -1376,7 +1607,8 @@ int utf8printv(char *buf, size_t bufsize, const char *fmt, va_list args) void **p = (void **)arg; *p = va_arg(args, void *); } - else if(type == 'e' || type == 'E' || type == 'f' || type == 'F' || type == 'g' || type == 'G') + else if(type == 'e' || type == 'E' || type == 'f' || type == 'F' || type == 'g' || + type == 'G' || type == 'a' || type == 'A') { double *i = (double *)arg; *i = va_arg(args, double); @@ -1841,6 +2073,36 @@ TEST_CASE("utf8printf printing floats", "[utf8printf]") CHECK(StringFormat::Fmt("%F", sqrt(negone)) == "NAN"); CHECK(StringFormat::Fmt("%F", -sqrt(negone)) == "NAN"); + CHECK(StringFormat::Fmt("%g", one / zero) == "+inf"); + CHECK(StringFormat::Fmt("%g", negone / zero) == "-inf"); + CHECK(StringFormat::Fmt("%g", sqrt(negone)) == "nan"); + CHECK(StringFormat::Fmt("%g", -sqrt(negone)) == "nan"); + + CHECK(StringFormat::Fmt("%G", one / zero) == "+INF"); + CHECK(StringFormat::Fmt("%G", negone / zero) == "-INF"); + CHECK(StringFormat::Fmt("%G", sqrt(negone)) == "NAN"); + CHECK(StringFormat::Fmt("%G", -sqrt(negone)) == "NAN"); + + CHECK(StringFormat::Fmt("%e", one / zero) == "+inf"); + CHECK(StringFormat::Fmt("%e", negone / zero) == "-inf"); + CHECK(StringFormat::Fmt("%e", sqrt(negone)) == "nan"); + CHECK(StringFormat::Fmt("%e", -sqrt(negone)) == "nan"); + + CHECK(StringFormat::Fmt("%E", one / zero) == "+INF"); + CHECK(StringFormat::Fmt("%E", negone / zero) == "-INF"); + CHECK(StringFormat::Fmt("%E", sqrt(negone)) == "NAN"); + CHECK(StringFormat::Fmt("%E", -sqrt(negone)) == "NAN"); + + CHECK(StringFormat::Fmt("%a", one / zero) == "+inf"); + CHECK(StringFormat::Fmt("%a", negone / zero) == "-inf"); + CHECK(StringFormat::Fmt("%a", sqrt(negone)) == "nan"); + CHECK(StringFormat::Fmt("%a", -sqrt(negone)) == "nan"); + + CHECK(StringFormat::Fmt("%A", one / zero) == "+INF"); + CHECK(StringFormat::Fmt("%A", negone / zero) == "-INF"); + CHECK(StringFormat::Fmt("%A", sqrt(negone)) == "NAN"); + CHECK(StringFormat::Fmt("%A", -sqrt(negone)) == "NAN"); + // subnormal value CHECK(StringFormat::Fmt("%.8e", 1.23456789e-310) == "1.23456789e-310"); CHECK(StringFormat::Fmt("%.8e", -1.23456789e-310) == "-1.23456789e-310"); @@ -1949,6 +2211,66 @@ TEST_CASE("utf8printf printing floats", "[utf8printf]") CHECK(StringFormat::Fmt("%-10.3f", 1.0) == "1.000 "); CHECK(StringFormat::Fmt("%010.3f", 1.0) == "000001.000"); }; + + SECTION("Hex float printing") + { + SECTION("Basics") + { + CHECK(StringFormat::Fmt("%a", 0.0) == "0x0p+0"); + CHECK(StringFormat::Fmt("%a", 1.0) == "0x1p+0"); + CHECK(StringFormat::Fmt("%a", 2.0) == "0x1p+1"); + CHECK(StringFormat::Fmt("%a", 3.0) == "0x1.8p+1"); + CHECK(StringFormat::Fmt("%a", 5.0) == "0x1.4p+2"); + CHECK(StringFormat::Fmt("%a", 0.1) == "0x1.999999999999ap-4"); + CHECK(StringFormat::Fmt("%a", 0.2) == "0x1.999999999999ap-3"); + CHECK(StringFormat::Fmt("%a", 0.25) == "0x1p-2"); + CHECK(StringFormat::Fmt("%a", 0.75) == "0x1.8p-1"); + CHECK(StringFormat::Fmt("%a", 1.234567890123456) == "0x1.3c0ca428c59f8p+0"); + CHECK(StringFormat::Fmt("%a", 1.234567123456) == "0x1.3c0c974bf5d73p+0"); + CHECK(StringFormat::Fmt("%a", 12345671234.56) == "0x1.6fedff2147ae1p+33"); + CHECK(StringFormat::Fmt("%a", 12345671234.56e+20) == "0x1.f2a33fa95047cp+99"); + CHECK(StringFormat::Fmt("%a", 12345671234.56e-20) == "0x1.0f7bf351a448p-33"); + + CHECK(StringFormat::Fmt("%A", 1.234567890123456) == "0X1.3C0CA428C59F8P+0"); + CHECK(StringFormat::Fmt("%A", 1.234567123456) == "0X1.3C0C974BF5D73P+0"); + CHECK(StringFormat::Fmt("%A", 12345671234.56) == "0X1.6FEDFF2147AE1P+33"); + CHECK(StringFormat::Fmt("%A", 12345671234.56e+20) == "0X1.F2A33FA95047CP+99"); + CHECK(StringFormat::Fmt("%A", 12345671234.56e-20) == "0X1.0F7BF351A448P-33"); + }; + + SECTION("Padding") + { + CHECK(StringFormat::Fmt("%10.3a", 1.2345) == "0x1.3c1p+0"); + CHECK(StringFormat::Fmt("%15.3a", 1.2345) == " 0x1.3c1p+0"); + CHECK(StringFormat::Fmt("%-15.3a", 1.2345) == "0x1.3c1p+0 "); + CHECK(StringFormat::Fmt("%015.3a", 1.2345) == "0x000001.3c1p+0"); + }; + + SECTION("Precision") + { + CHECK(StringFormat::Fmt("%.3a", 0.0) == "0x0.000p+0"); + CHECK(StringFormat::Fmt("%.3a", 1.0) == "0x1.000p+0"); + CHECK(StringFormat::Fmt("%.3a", 2.0) == "0x1.000p+1"); + CHECK(StringFormat::Fmt("%.3a", 0.1) == "0x1.99ap-4"); + CHECK(StringFormat::Fmt("%.3a", 0.2) == "0x1.99ap-3"); + CHECK(StringFormat::Fmt("%.3a", 1.2313) == "0x1.3b3p+0"); + + CHECK(StringFormat::Fmt("%.0a", 0.0) == "0x0p+0"); + + CHECK(StringFormat::Fmt("%.1a", 0.2) == "0x1.ap-3"); + CHECK(StringFormat::Fmt("%.0a", 0.2) == "0x2p-3"); + CHECK(StringFormat::Fmt("%.a", 0.2) == "0x2p-3"); + }; + + SECTION("Rounding") + { + CHECK(StringFormat::Fmt("%.5a", 0.12345) == "0x1.f9a6bp-4"); + CHECK(StringFormat::Fmt("%.6a", 0.12345) == "0x1.f9a6b5p-4"); + + CHECK(StringFormat::Fmt("%.5a", 0.12345002) == "0x1.f9a6cp-4"); + CHECK(StringFormat::Fmt("%.6a", 0.12345002) == "0x1.f9a6bap-4"); + }; + }; }; #endif // ENABLED(ENABLE_UNIT_TESTS)