Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions stdio/format.c
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ static int format_sprintfScientificForm(struct buffer *buff, struct bigdouble *b
}


static int format_sprintfDouble(void *ctx, feedfunc feed, double d, uint32_t flags, int minFieldWidth, int precision, char format)
static int format_sprintfDouble(void *ctx, feedfunc feed, double d, uint32_t flags, int minFieldWidth, int precision, char format, uint8_t msb)
{
struct buffer buff;
char sign = 0;
Expand All @@ -640,7 +640,7 @@ static int format_sprintfDouble(void *ctx, feedfunc feed, double d, uint32_t fla
}

/* check sign */
if (((num64 >> 63) & 1) != 0) {
if (msb != 0) {
sign = '-';
d = -d;
}
Expand All @@ -654,6 +654,7 @@ static int format_sprintfDouble(void *ctx, feedfunc feed, double d, uint32_t fla
sign = '\0';
}
num64 = format_u64FromDouble(d);
num64 |= ((uint64_t)msb << 63);

CHECK_FAIL(ret, format_bufferInit(&buff, 2 * precision + 6));

Expand Down Expand Up @@ -1178,19 +1179,26 @@ int format_parse(void *ctx, feedfunc feed, const char *format, va_list args)
case 'f':
case 'g': {
#ifndef IO_NO_FLOAT
double doubleNumber;
long double doubleNumber;
uint8_t msb = 0;

if (fmt == 'g' || fmt == 'G') {
flags |= FLAG_NO_TRAILING_ZEROS;
}
if ((flags & FLAG_LONG_DOUBLE) != 0) {
/* NOTE: support for long double is incomplete */
doubleNumber = (double)va_arg((args), long double);
doubleNumber = va_arg((args), long double);
}
else {
doubleNumber = (double)va_arg((args), double);
doubleNumber = (long double)va_arg((args), double);
}

/* preserve sign bit during down-conversion to double */
if (__builtin_signbit(doubleNumber) != 0) {
msb = 1;
}

CHECK_FAIL(ret, format_sprintfDouble(ctx, feed, doubleNumber, flags, minFieldWidth, precision, tolower(fmt)));
CHECK_FAIL(ret, format_sprintfDouble(ctx, feed, (double)doubleNumber, flags, minFieldWidth, precision, tolower(fmt), msb));
Comment on lines +1182 to +1201

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Casting standard double values to long double and then back to double introduces significant performance overhead on platforms without hardware 128-bit float support (such as RISC-V), as it forces the use of soft-float emulation routines (__extenddftf2 and __trunctfdf2) for every standard double formatting.

Additionally, this unnecessarily subjects standard double formatting to the buggy __trunctfdf2 emulation routine, which could lead to incorrect sign behavior even for standard double values.

We can avoid this overhead and risk by keeping doubleNumber as a standard double and only using long double locally when FLAG_LONG_DOUBLE is set.

double doubleNumber;
				uint8_t msb = 0;

				if (fmt == 'g' || fmt == 'G') {
					flags |= FLAG_NO_TRAILING_ZEROS;
				}
				if ((flags & FLAG_LONG_DOUBLE) != 0) {
					/* NOTE: support for long double is incomplete */
					long double ld = va_arg((args), long double);
					if (__builtin_signbit(ld) != 0) {
						msb = 1;
					}
					doubleNumber = (double)ld;
				}
				else {
					doubleNumber = va_arg((args), double);
					if (__builtin_signbit(doubleNumber) != 0) {
						msb = 1;
					}
				}

				CHECK_FAIL(ret, format_sprintfDouble(ctx, feed, doubleNumber, flags, minFieldWidth, precision, tolower(fmt), msb));
References
  1. Avoid unnecessary type promotion/demotion overhead (such as double to long double) on soft-float platforms to prevent performance degradation and potential emulation bugs.

break;
#else
if ((flags & FLAG_LONG_DOUBLE) != 0) {
Expand Down
Loading