mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Fix formatting of very long fixed-point numbers
This commit is contained in:
@@ -29,50 +29,42 @@ static size_t parseNumber(char const *spec, size_t &value) {
|
|||||||
|
|
||||||
size_t FormatSpec::parseSpec(char const *spec) {
|
size_t FormatSpec::parseSpec(char const *spec) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
// <sign>
|
// <sign>
|
||||||
if (char c = spec[i]; c == ' ' || c == '+') {
|
if (char c = spec[i]; c == ' ' || c == '+') {
|
||||||
++i;
|
++i;
|
||||||
sign = c;
|
sign = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <exact>
|
// <exact>
|
||||||
if (spec[i] == '#') {
|
if (spec[i] == '#') {
|
||||||
++i;
|
++i;
|
||||||
exact = true;
|
exact = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <align>
|
// <align>
|
||||||
if (spec[i] == '-') {
|
if (spec[i] == '-') {
|
||||||
++i;
|
++i;
|
||||||
alignLeft = true;
|
alignLeft = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <pad>
|
// <pad>
|
||||||
if (spec[i] == '0') {
|
if (spec[i] == '0') {
|
||||||
++i;
|
++i;
|
||||||
padZero = true;
|
padZero = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <width>
|
// <width>
|
||||||
if (isDigit(spec[i])) {
|
if (isDigit(spec[i])) {
|
||||||
i += parseNumber(&spec[i], width);
|
i += parseNumber(&spec[i], width);
|
||||||
}
|
}
|
||||||
|
|
||||||
// <frac>
|
// <frac>
|
||||||
if (spec[i] == '.') {
|
if (spec[i] == '.') {
|
||||||
++i;
|
++i;
|
||||||
hasFrac = true;
|
hasFrac = true;
|
||||||
i += parseNumber(&spec[i], fracWidth);
|
i += parseNumber(&spec[i], fracWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// <prec>
|
// <prec>
|
||||||
if (spec[i] == 'q') {
|
if (spec[i] == 'q') {
|
||||||
++i;
|
++i;
|
||||||
hasPrec = true;
|
hasPrec = true;
|
||||||
i += parseNumber(&spec[i], precision);
|
i += parseNumber(&spec[i], precision);
|
||||||
}
|
}
|
||||||
|
|
||||||
// <type>
|
// <type>
|
||||||
switch (char c = spec[i]; c) {
|
switch (char c = spec[i]; c) {
|
||||||
case 'd':
|
case 'd':
|
||||||
@@ -87,7 +79,7 @@ size_t FormatSpec::parseSpec(char const *spec) {
|
|||||||
type = c;
|
type = c;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Done parsing
|
||||||
parsed = true;
|
parsed = true;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -188,36 +180,32 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
if (useType == 'd' || useType == 'f') {
|
if (useType == 'd' || useType == 'f') {
|
||||||
if (int32_t v = value; v < 0) {
|
if (int32_t v = value; v < 0) {
|
||||||
signChar = '-';
|
signChar = '-';
|
||||||
if (v != INT32_MIN) {
|
if (v != INT32_MIN) { // -INT32_MIN is UB
|
||||||
value = -v;
|
value = -v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char prefixChar = !useExact ? 0
|
// The longest possible formatted number is fixed-point with 10 digits, 255 fractional digits,
|
||||||
: useType == 'X' ? '$'
|
// and a precision suffix, for 270 total bytes (counting the NUL terminator).
|
||||||
: useType == 'x' ? '$'
|
// (Actually 269 since a 2-digit precision cannot reach 10 integer digits.)
|
||||||
: useType == 'b' ? '%'
|
// Make the buffer somewhat larger just in case.
|
||||||
: useType == 'o' ? '&'
|
char valueBuf[300];
|
||||||
: 0;
|
|
||||||
|
|
||||||
char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
|
|
||||||
|
|
||||||
if (useType == 'b') {
|
if (useType == 'b') {
|
||||||
// Special case for binary
|
// Special case for binary (since `snprintf` doesn't support it)
|
||||||
char *ptr = valueBuf;
|
|
||||||
|
|
||||||
|
// Buffer the digits from least to greatest
|
||||||
|
char *ptr = valueBuf;
|
||||||
do {
|
do {
|
||||||
*ptr++ = (value & 1) + '0';
|
*ptr++ = (value & 1) + '0';
|
||||||
value >>= 1;
|
value >>= 1;
|
||||||
} while (value);
|
} while (value);
|
||||||
|
|
||||||
// Reverse the digits
|
// Reverse the digits and terminate the string
|
||||||
std::reverse(valueBuf, ptr);
|
std::reverse(valueBuf, ptr);
|
||||||
|
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
} else if (useType == 'f') {
|
} else if (useType == 'f') {
|
||||||
// Special case for fixed-point
|
// Special case for fixed-point (since it needs fractional part and precision)
|
||||||
|
|
||||||
// Default fractional width (C++'s is 6 for "%f"; here 5 is enough for Q16.16)
|
// Default fractional width (C++'s is 6 for "%f"; here 5 is enough for Q16.16)
|
||||||
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
||||||
@@ -226,6 +214,7 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
useFracWidth = 255;
|
useFracWidth = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default precision taken from default `-Q` option
|
||||||
size_t defaultPrec = options.fixPrecision;
|
size_t defaultPrec = options.fixPrecision;
|
||||||
size_t usePrec = hasPrec ? precision : defaultPrec;
|
size_t usePrec = hasPrec ? precision : defaultPrec;
|
||||||
if (usePrec < 1 || usePrec > 31) {
|
if (usePrec < 1 || usePrec > 31) {
|
||||||
@@ -237,29 +226,30 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
usePrec = defaultPrec;
|
usePrec = defaultPrec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Floating-point formatting works for all fixed-point values
|
||||||
double fval = fabs(value / pow(2.0, usePrec));
|
double fval = fabs(value / pow(2.0, usePrec));
|
||||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||||
} else {
|
} else {
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||||
}
|
}
|
||||||
} else if (useType == 'd') {
|
|
||||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
|
||||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
|
||||||
// printed later from `signChar`.
|
|
||||||
uint32_t uval =
|
|
||||||
value != static_cast<uint32_t>(INT32_MIN) ? labs(static_cast<int32_t>(value)) : value;
|
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, uval);
|
|
||||||
} else {
|
} else {
|
||||||
char const *spec = useType == 'u' ? "%" PRIu32
|
// `value` has already been made non-negative, so type 'd' is OK here even for `INT32_MIN`.
|
||||||
: useType == 'X' ? "%" PRIX32
|
// The sign will be printed later from `signChar`.
|
||||||
: useType == 'x' ? "%" PRIx32
|
char const *spec = useType == 'd' || useType == 'u' ? "%" PRIu32
|
||||||
: useType == 'o' ? "%" PRIo32
|
: useType == 'X' ? "%" PRIX32
|
||||||
: "%" PRIu32;
|
: useType == 'x' ? "%" PRIx32
|
||||||
|
: useType == 'o' ? "%" PRIo32
|
||||||
|
: "%" PRIu32;
|
||||||
snprintf(valueBuf, sizeof(valueBuf), spec, value);
|
snprintf(valueBuf, sizeof(valueBuf), spec, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char prefixChar = !useExact ? 0
|
||||||
|
: useType == 'X' || useType == 'x' ? '$'
|
||||||
|
: useType == 'b' ? '%'
|
||||||
|
: useType == 'o' ? '&'
|
||||||
|
: 0;
|
||||||
|
|
||||||
size_t valueLen = strlen(valueBuf);
|
size_t valueLen = strlen(valueBuf);
|
||||||
size_t numLen = (signChar != 0) + (prefixChar != 0) + valueLen;
|
size_t numLen = (signChar != 0) + (prefixChar != 0) + valueLen;
|
||||||
size_t totalLen = width > numLen ? width : numLen;
|
size_t totalLen = width > numLen ? width : numLen;
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ ENDM
|
|||||||
test $8000_0000 ; INT32_MIN
|
test $8000_0000 ; INT32_MIN
|
||||||
test $0000_0000 ; UINT32_MIN
|
test $0000_0000 ; UINT32_MIN
|
||||||
test $ffff_ffff ; UINT32_MAX
|
test $ffff_ffff ; UINT32_MAX
|
||||||
|
|
||||||
|
println strfmt("%#.255q1f", $7fff_ffff)
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ $7fffffff = &17777777777 = %01111111111111111111111111111111 = 2147483647U = +21
|
|||||||
$80000000 = &20000000000 = %10000000000000000000000000000000 = 2147483648U = -2147483648 = -32768.0000000000000000
|
$80000000 = &20000000000 = %10000000000000000000000000000000 = 2147483648U = -2147483648 = -32768.0000000000000000
|
||||||
$00000000 = &00000000000 = %00000000000000000000000000000000 = 0U = +0 = +0.0000000000000000
|
$00000000 = &00000000000 = %00000000000000000000000000000000 = 0U = +0 = +0.0000000000000000
|
||||||
$ffffffff = &37777777777 = %11111111111111111111111111111111 = 4294967295U = -1 = -0.0000152587890625
|
$ffffffff = &37777777777 = %11111111111111111111111111111111 = 4294967295U = -1 = -0.0000152587890625
|
||||||
|
1073741823.500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000q1
|
||||||
|
|||||||
Reference in New Issue
Block a user