Skip to content

Commit 496e5aa

Browse files
committed
Implemented custom log10/pow in _etoa to eliminate dependency on <math.h> for %E format
1 parent 0fea7e5 commit 496e5aa

1 file changed

Lines changed: 36 additions & 13 deletions

File tree

printf.c

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,13 @@ static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t
316316

317317

318318
#if defined(PRINTF_SUPPORT_FLOAT)
319-
#include <math.h>
319+
#include <float.h>
320320
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
321321
// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
322322
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);
323323
#endif
324324

325+
// internal ftoa for fixed decimal floating point
325326
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)
326327
{
327328
char buf[PRINTF_FTOA_BUFFER_SIZE];
@@ -452,6 +453,8 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d
452453
}
453454

454455
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
456+
// internal ftoa variant for exponential floating-point type
457+
// contributed by Martijn Jasperse <m.jasperse@gmail.com>
455458
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)
456459
{
457460
// check for special values
@@ -462,29 +465,49 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d
462465
bool negative = value < 0;
463466
if (negative) value = -value;
464467

465-
// determine the decimal exponent
466-
int expval = (int)floor(log10(value)); // "value" must be +ve
467-
468-
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
469-
unsigned int minwidth = ((expval < 100)&&(expval > -100)) ? 4 : 5;
470-
471468
// default precision
472469
if (!(flags & FLAGS_PRECISION)) {
473470
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
474471
}
475472

473+
// determine the decimal exponent
474+
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
475+
union {
476+
uint64_t U;
477+
double F;
478+
} conv;
479+
conv.F = value;
480+
int exp2 = (int)((conv.U >> 52) & 0x07FF) - 1023; // effectively log2
481+
conv.U = (conv.U & ((1ULL << 52) - 1)) | (1023ULL << 52); // drop the exponent so conv.F is now in [1,2)
482+
// now approximate log10 from the log2 integer part and an expansion of ln around 1.5
483+
int expval = (int)(0.1760912590558 + exp2*0.301029995663981 + (conv.F - 1.5)*0.289529654602168);
484+
// now we want to compute 10^expval but we want to be sure it won't overflow
485+
exp2 = (int)(expval*3.321928094887362 + 0.5);
486+
double z = expval*2.302585092994046 - exp2*0.6931471805599453;
487+
double z2 = z*z;
488+
conv.U = (uint64_t)(exp2 + 1023) << 52;
489+
// compute exp(z) using continued fractions
490+
// https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
491+
conv.F *= 1 + 2*z/(2 - z + (z2/(6 + (z2/(10 + z2/14)))));
492+
// correct for rounding errors
493+
if (value < conv.F) {
494+
expval--;
495+
conv.F /= 10;
496+
}
497+
498+
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
499+
unsigned int minwidth = ((expval < 100)&&(expval > -100)) ? 4 : 5;
500+
476501
// in "%g" mode, "prec" is the number of *significant figures* not decimals
477502
if (flags & FLAGS_ADAPT_EXP) {
478-
// do we want to fall-back to "%f" mode for small number?
479-
if ((expval > -5)&&(expval < 6)) {
503+
// do we want to fall-back to "%f" mode?
504+
if ((value >= 1e-4)&&(value < 1e6)) {
480505
if ((int)prec > expval) {
481506
prec = (unsigned)((int)prec - expval - 1);
482507
} else {
483508
prec = 0;
484509
}
485-
// TODO: there's also a special case where we're supposed to ELIMINATE digits from the whole part
486-
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
487-
510+
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
488511
// no characters in exponent
489512
minwidth = 0;
490513
expval = 0;
@@ -508,7 +531,7 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d
508531
}
509532

510533
// rescale the float value
511-
if (expval) value *= pow(10.0, -expval);
534+
if (expval) value /= conv.F;
512535

513536
// output the floating part
514537
const size_t start_idx = idx;

0 commit comments

Comments
 (0)