Skip to content

Commit d61d014

Browse files
committed
Merge exponential support
2 parents 369b7bb + 496e5aa commit d61d014

3 files changed

Lines changed: 252 additions & 59 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Therefore I decided to write an own, final implementation which meets the follow
2828
- Support of decimal/floating number representation (with an own fast itoa/ftoa)
2929
- Reentrant and thread-safe, malloc free, no static vars/buffers
3030
- LINT and compiler L4 warning free, mature, coverity clean, automotive ready
31-
- Extensive test suite (> 370 test cases) passing
31+
- Extensive test suite (> 390 test cases) passing
3232
- Simply the best *printf* around the net
3333
- MIT license
3434

@@ -92,6 +92,8 @@ The following format specifiers are supported:
9292
| x | Unsigned hexadecimal integer (lowercase) |
9393
| X | Unsigned hexadecimal integer (uppercase) |
9494
| f or F | Decimal floating point |
95+
| e or E | Scientific-notation (exponential) floating point |
96+
| g or G | Scientific or decimal floating point |
9597
| c | Single character |
9698
| s | String of characters |
9799
| p | Pointer address |
@@ -164,6 +166,7 @@ int length = sprintf(NULL, "Hello, world"); // length is set to 12
164166
| PRINTF_NTOA_BUFFER_SIZE | 32 | ntoa (integer) conversion buffer size. This must be big enough to hold one converted numeric number _including_ leading zeros, normally 32 is a sufficient value. Created on the stack |
165167
| PRINTF_FTOA_BUFFER_SIZE | 32 | ftoa (float) conversion buffer size. This must be big enough to hold one converted float number _including_ leading zeros, normally 32 is a sufficient value. Created on the stack |
166168
| PRINTF_DISABLE_SUPPORT_FLOAT | undefined | Define this to disable floating point (%f) support |
169+
| PRINTF_DISABLE_SUPPORT_EXPONENTIAL | undefined | Define this to disable exponential floating point (%e) support |
167170
| PRINTF_DISABLE_SUPPORT_LONG_LONG | undefined | Define this to disable long long (%ll) support |
168171
| PRINTF_DISABLE_SUPPORT_PTRDIFF_T | undefined | Define this to disable ptrdiff_t (%t) support |
169172

printf.c

Lines changed: 173 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@
6464
#define PRINTF_SUPPORT_FLOAT
6565
#endif
6666

67+
// support for exponential floating point notation (%e/%g)
68+
#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
69+
#define PRINTF_SUPPORT_EXPONENTIAL
70+
#endif
71+
72+
// define the default floating point precision
73+
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
74+
#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
75+
#endif
76+
77+
// define the largest float suitable to print with %f
78+
#ifndef PRINTF_MAX_FLOAT
79+
#define PRINTF_MAX_FLOAT 1e9
80+
#endif
81+
6782
// support for the long long types (%llu or %p)
6883
// default: activated
6984
#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
@@ -91,6 +106,7 @@
91106
#define FLAGS_LONG (1U << 8U)
92107
#define FLAGS_LONG_LONG (1U << 9U)
93108
#define FLAGS_PRECISION (1U << 10U)
109+
#define FLAGS_ADAPT_EXP (1U << 11U)
94110

95111

96112
// output function type
@@ -169,12 +185,34 @@ static unsigned int _atoi(const char** str)
169185
return i;
170186
}
171187

188+
// output the specified string in reverse, taking care of any zero-padding
189+
static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
190+
{
191+
const size_t start_idx = idx;
192+
193+
// pad spaces up to given width
194+
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
195+
for (size_t i = len; i < width; i++) {
196+
out(' ', buffer, idx++, maxlen);
197+
}
198+
}
199+
200+
// reverse string
201+
while (len) out(buf[--len], buffer, idx++, maxlen);
202+
203+
// append pad spaces up to given width
204+
if (flags & FLAGS_LEFT) {
205+
while (idx - start_idx < width) {
206+
out(' ', buffer, idx++, maxlen);
207+
}
208+
}
209+
210+
return idx;
211+
}
172212

173213
// internal itoa format
174214
static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
175215
{
176-
const size_t start_idx = idx;
177-
178216
// pad leading zeros
179217
if (!(flags & FLAGS_LEFT)) {
180218
if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
@@ -222,26 +260,7 @@ static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t ma
222260
}
223261
}
224262

225-
// pad spaces up to given width
226-
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
227-
for (size_t i = len; i < width; i++) {
228-
out(' ', buffer, idx++, maxlen);
229-
}
230-
}
231-
232-
// reverse string
233-
for (size_t i = 0U; i < len; i++) {
234-
out(buf[len - i - 1U], buffer, idx++, maxlen);
235-
}
236-
237-
// append pad spaces up to given width
238-
if (flags & FLAGS_LEFT) {
239-
while (idx - start_idx < width) {
240-
out(' ', buffer, idx++, maxlen);
241-
}
242-
}
243-
244-
return idx;
263+
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
245264
}
246265

247266

@@ -296,26 +315,38 @@ static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t
296315

297316

298317
#if defined(PRINTF_SUPPORT_FLOAT)
318+
#include <float.h>
319+
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
320+
// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
321+
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
322+
#endif
323+
324+
// internal ftoa for fixed decimal floating point
299325
static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
300326
{
301-
const size_t start_idx = idx;
302-
303327
char buf[PRINTF_FTOA_BUFFER_SIZE];
304328
size_t len = 0U;
305329
double diff = 0.0;
306330

307-
// if input is larger than thres_max, revert to exponential
308-
const double thres_max = (double)0x7FFFFFFF;
309-
310331
// powers of 10
311332
static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
312333

313-
// test for NaN
314-
if (value != value) {
315-
out('n', buffer, idx++, maxlen);
316-
out('a', buffer, idx++, maxlen);
317-
out('n', buffer, idx++, maxlen);
318-
return idx;
334+
// test for special values
335+
if (value != value)
336+
return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
337+
if (value < -DBL_MAX)
338+
return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
339+
if (value > DBL_MAX)
340+
return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4 : 3, width, flags);
341+
342+
// test for very large values
343+
// standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
344+
if ((value > PRINTF_MAX_FLOAT)||(value < -PRINTF_MAX_FLOAT)) {
345+
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
346+
return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
347+
#else
348+
return 0U;
349+
#endif
319350
}
320351

321352
// test for negative
@@ -325,9 +356,9 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d
325356
value = 0 - value;
326357
}
327358

328-
// set default precision to 6, if not set explicitly
359+
// set default precision, if not set explicitly
329360
if (!(flags & FLAGS_PRECISION)) {
330-
prec = 6U;
361+
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
331362
}
332363
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
333364
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
@@ -355,12 +386,6 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d
355386
++frac;
356387
}
357388

358-
// TBD: for very large numbers switch back to native sprintf for exponentials. Anyone want to write code to replace this?
359-
// Normal printf behavior is to print EVERY whole number digit which can be 100s of characters overflowing your buffers == bad
360-
if (value > thres_max) {
361-
return 0U;
362-
}
363-
364389
if (prec == 0U) {
365390
diff = value - (double)whole;
366391
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
@@ -419,27 +444,109 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d
419444
}
420445
}
421446

422-
// pad spaces up to given width
423-
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
424-
for (size_t i = len; i < width; i++) {
425-
out(' ', buffer, idx++, maxlen);
447+
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
448+
}
449+
450+
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
451+
// internal ftoa variant for exponential floating-point type
452+
// contributed by Martijn Jasperse <m.jasperse@gmail.com>
453+
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
454+
{
455+
// check for special values
456+
if ((value != value)||(value > DBL_MAX)||(value < -DBL_MAX))
457+
return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
458+
459+
// determine the sign
460+
bool negative = value < 0;
461+
if (negative) value = -value;
462+
463+
// default precision
464+
if (!(flags & FLAGS_PRECISION)) {
465+
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
466+
}
467+
468+
// determine the decimal exponent
469+
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
470+
union {
471+
uint64_t U;
472+
double F;
473+
} conv;
474+
conv.F = value;
475+
int exp2 = (int)((conv.U >> 52) & 0x07FF) - 1023; // effectively log2
476+
conv.U = (conv.U & ((1ULL << 52) - 1)) | (1023ULL << 52); // drop the exponent so conv.F is now in [1,2)
477+
// now approximate log10 from the log2 integer part and an expansion of ln around 1.5
478+
int expval = (int)(0.1760912590558 + exp2*0.301029995663981 + (conv.F - 1.5)*0.289529654602168);
479+
// now we want to compute 10^expval but we want to be sure it won't overflow
480+
exp2 = (int)(expval*3.321928094887362 + 0.5);
481+
double z = expval*2.302585092994046 - exp2*0.6931471805599453;
482+
double z2 = z*z;
483+
conv.U = (uint64_t)(exp2 + 1023) << 52;
484+
// compute exp(z) using continued fractions
485+
// https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
486+
conv.F *= 1 + 2*z/(2 - z + (z2/(6 + (z2/(10 + z2/14)))));
487+
// correct for rounding errors
488+
if (value < conv.F) {
489+
expval--;
490+
conv.F /= 10;
491+
}
492+
493+
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
494+
unsigned int minwidth = ((expval < 100)&&(expval > -100)) ? 4 : 5;
495+
496+
// in "%g" mode, "prec" is the number of *significant figures* not decimals
497+
if (flags & FLAGS_ADAPT_EXP) {
498+
// do we want to fall-back to "%f" mode?
499+
if ((value >= 1e-4)&&(value < 1e6)) {
500+
if ((int)prec > expval) {
501+
prec = (unsigned)((int)prec - expval - 1);
502+
} else {
503+
prec = 0;
504+
}
505+
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
506+
// no characters in exponent
507+
minwidth = 0;
508+
expval = 0;
509+
} else {
510+
// we use one sigfig for the whole part
511+
if ((prec > 0)&&(flags & FLAGS_PRECISION)) --prec;
426512
}
427513
}
428-
429-
// reverse string
430-
for (size_t i = 0U; i < len; i++) {
431-
out(buf[len - i - 1U], buffer, idx++, maxlen);
514+
// will everything fit?
515+
unsigned int fwidth = width;
516+
if (width > minwidth) {
517+
// we didn't fall-back so subtract the characters required for the exponent
518+
fwidth -= minwidth;
519+
} else {
520+
// not enough characters, so go back to default sizing
521+
fwidth = 0;
522+
}
523+
if ((flags & FLAGS_LEFT) && minwidth) {
524+
// if we're padding on the right, DON'T pad the floating part
525+
fwidth = 0;
432526
}
433527

434-
// append pad spaces up to given width
435-
if (flags & FLAGS_LEFT) {
436-
while (idx - start_idx < width) {
437-
out(' ', buffer, idx++, maxlen);
528+
// rescale the float value
529+
if (expval) value /= conv.F;
530+
531+
// output the floating part
532+
const size_t start_idx = idx;
533+
idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
534+
535+
// output the exponent part
536+
if (minwidth) {
537+
// output the exponential symbol
538+
out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
539+
// output the exponent value
540+
idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
541+
// might need to right-pad spaces
542+
if (flags & FLAGS_LEFT) {
543+
while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
438544
}
439545
}
440-
441546
return idx;
442547
}
548+
549+
#endif // PRINTF_SUPPORT_EXPONENTIAL
443550
#endif // PRINTF_SUPPORT_FLOAT
444551

445552

@@ -627,9 +734,21 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const
627734
#if defined(PRINTF_SUPPORT_FLOAT)
628735
case 'f' :
629736
case 'F' :
737+
if (*format == 'F') flags |= FLAGS_UPPERCASE;
630738
idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
631739
format++;
632740
break;
741+
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
742+
case 'e':
743+
case 'E':
744+
case 'g':
745+
case 'G':
746+
if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
747+
if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
748+
idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
749+
format++;
750+
break;
751+
#endif // PRINTF_SUPPORT_EXPONENTIAL
633752
#endif // PRINTF_SUPPORT_FLOAT
634753
case 'c' : {
635754
unsigned int l = 1U;

0 commit comments

Comments
 (0)