#ifndef FTOA_H #define FTOA_H //TODO: for fractional digits/non-sci, determine the right amount of left padding (more if the whole number is indeed <1, to keep the significant digits), less if sci notation and/or mantissa has sig. digits (don't want N before . and N after!) #ifndef FTOA_ROUNDTRIP # define FTOA_ROUNDTRIP 1 #endif #ifndef FTOA_DEBUG # define FTOA_DEBUG 0 #endif #ifndef FTOA_USE_SPRINTF #define FTOA_USE_SPRINTF 0 #endif #if FTOA_DEBUG # define FTOAassert(x) assert(x) # define DBFTOA(x) std::cerr<<"\nFTOA " <<__func__<<"("<<__LINE__<<"): " #x "="<<x<<"\n" # define DBFTOA2(x0,x1) std::cerr<<"\nFTOA " <<__func__<<"("<<__LINE__<<"): " #x0 "="<<x0<<" " #x1 "="<<x1 <<"\n" #else # define FTOAassert(x) # define DBFTOA(x) # define DBFTOA2(x0,x1) #endif /* DECIMAL_FOR_WHOLE ; ftos(123) 0 ; 123 1 ; 123 2 ; 123. ; ftos(0) is always just "0" (not "0.0") ; ftos(.01) 0 ; .01 1 ; 0.01 2 ; 0.01 */ #ifndef DECIMAL_FOR_WHOLE # define DECIMAL_FOR_WHOLE 1 #endif #include <limits> #include <stdint.h> #include <iostream> #include <cmath> #include <assert.h> #include <cstdio> #include "utoa.h" #include "nan.h" template <class Float> struct ftoa_traits { }; //eP10, // sigd decimal places normally printed, roundtripd needed so that round-trip float->string->float is identity #define DEFINE_FTOA_TRAITS(FLOATT,INTT,sigd,roundtripd,small,large,used,P10) \ template <> \ struct ftoa_traits<FLOATT> { \ typedef INTT int_t; \ typedef u ## INTT uint_t; \ typedef FLOATT float_t; \ enum { digits10=std::numeric_limits<INTT>::digits10, chars_block=P10, usedig=used, sigdig=sigd, roundtripdig=roundtripd, bufsize=roundtripdig+7 }; \ static const double pow10_block = 1e ## P10; \ static const float_t small_f = small; \ static const float_t large_f = large; \ static inline int sprintf(char *buf,double f) { return std::sprintf(buf,"%." #used "g",f); } \ static inline int sprintf_sci(char *buf,double f) { return std::sprintf(buf,"%." #used "e",f); } \ static inline int sprintf_nonsci(char *buf,double f) { return std::sprintf(buf,"%." #used "f",f); } \ static inline uint_t fracblock(double frac) { FTOAassert(frac>=0 && frac<1); double f=frac*pow10_block;uint_t i=(uint_t)f;FTOAassert(i<pow10_block);return i; } \ static inline uint_t rounded_fracblock(double frac) { FTOAassert(frac>=0 && frac<1); double f=frac*pow10_block;uint_t i=(uint_t)(f+.5);FTOAassert(i<pow10_block);return i; } \ static inline float_t mantexp10(float_t f,int &exp) { float_t e=std::log10(f); float_t ef=std::floor(e); exp=ef; return f/std::pow((float_t)10,ef); } \ static inline bool use_sci_abs(float_t fa) { return fa<small || fa>large; } \ static inline bool use_sci(float_t f) { return use_sci_abs(std::fabs(f)); } \ }; //TODO: decide on computations in double (would hurt long double) or in native float type - any advantage? more precision is usually better. //10^22 = 0x1.0f0cf064dd592p73 is the largest exactly representable power of 10 in the binary64 format. but round down to 18 so int64_t can hold it. #if FTOA_ROUNDTRIP #define DEFINE_FTOA_TRAITS_ROUNDTRIP(FLOATT,INTT,sigd,roundtripd,small,large) DEFINE_FTOA_TRAITS(FLOATT,INTT,sigd,roundtripd,small,large,roundtripd,roundtripd) #else #define DEFINE_FTOA_TRAITS_ROUNDTRIP(FLOATT,INTT,sigd,roundtripd,small,large) DEFINE_FTOA_TRAITS(FLOATT,INTT,sigd,roundtripd,small,large,sigd,sigd) #endif DEFINE_FTOA_TRAITS_ROUNDTRIP(double,int64_t,15,17,1e-5,1e8) //i've heard that 1e10 is fine for float. but we only have 1e9 (9 decimal places) in int32. DEFINE_FTOA_TRAITS_ROUNDTRIP(float,int32_t,6,9,1e-3,1e8) template <class F> inline void ftoa_error(F f,char const* msg="") { using namespace std; cerr<<"ftoa error: "<<msg<<" f="<<f<<endl; assert(!"ftoa error"); } // all of the below prepend and return new cursor. null terminate yourself (like itoa/utoa) //possibly empty string for ~0 (no sci notation fallback). left padded with the right number of 0s (tricky). [ret,p) are the digits. template <class F> char *prepend_pos_frac_digits(char *p,F f) { FTOAassert(f<1 && f >0); typedef ftoa_traits<F> FT; //repeat if very small??? nah, require sci notation to take care of it. typename FT::uint_t i=FT::rounded_fracblock(f); DBFTOA2(f,i); if (i>0) { unsigned n_skipped; char *d=utoa_drop_trailing_0(p,i,n_skipped); char *b=p-FT::chars_block+n_skipped; FTOAassert(b<=d); left_pad(b,d,'0'); return b; } else { return p; } } template <class F> char *append_pos_frac_digits(char *p,F f) { // '0' right-padded, nul terminated, return position of nul. [p,ret) are the digits if (f==0) { *p++='0'; return p; } FTOAassert(f<1 && f >0); typedef ftoa_traits<F> FT; //repeat if very small??? nah, require sci notation to take care of it. typename FT::uint_t i=FT::rounded_fracblock(f); DBFTOA2(f,i); if (i>0) { char *e=p+FT::chars_block; utoa_left_pad(p,e,i,'0'); *e=0; return e; } else { *p=0; return p; } } template <class F> inline char *prepend_pos_frac(char *p,F f) { FTOAassert(f<1 && f>=0); if (f==0) { *--p='0'; return p; } p=prepend_pos_frac_digits(p,f); *--p='.'; if (DECIMAL_FOR_WHOLE>0) *--p='0'; return p; } template <class F> inline char *append_pos_frac(char *p,F f) { DBFTOA(f); if (DECIMAL_FOR_WHOLE>0) *p++='0'; *p++='.'; return append_pos_frac_digits(p,f); } template <class F> inline char *prepend_frac(char *p,F f,bool positive_sign=false) { FTOAassert(f<1 && f>-1); if (f==0) *--p='0'; else if (f<0) { p=prepend_pos_frac(p,-f); *--p='-'; } else { p=prepend_pos_frac(p,f); if (positive_sign) *--p='+'; } return p; } template <class F> inline char *append_sign(char *p,F f,bool positive_sign=false) { if (f<0) { *p++='-'; } else if (positive_sign) *p++='+'; return p; } template <class F> inline char *append_frac(char *p,F f,bool positive_sign=false) { FTOAassert(f<1 && f>-1); if (f==0) { *p++='0'; return p; } else if (f<0) { *p++='-'; return append_pos_frac(p,-f); } if (positive_sign) { *p++='+'; return append_pos_frac(p,f); } } //append_frac, append_pos_sci, append_sci. notice these are all composed according to a pattern (but reversing order of composition in pre vs app). or can implement with copy through buffer /* will switch to sci notation if integer part is too big for the int type. but for very small values, will simply display 0 (i.e. //TODO: find out log10 and leftpad 0s then convert rest) */ template <class F> char *prepend_pos_nonsci(char *p,F f) { typedef ftoa_traits<F> FT; typedef typename FT::uint_t uint_t; DBFTOA(f); FTOAassert(f>0); if (f>std::numeric_limits<uint_t>::max()) return prepend_pos_sci(p,f); //which is faster - modf is weird and returns negative frac part if f is negative. while we could deal with this using fabs, we instead only handle positive here (put - sign in front and negate, then call us) - ? #if 0 F intpart; F frac=std::modf(f,&intpart); uint_t u=intpart; #else uint_t u=f; F frac=f-u; #endif DBFTOA2(u,frac); if (frac == 0) { if (DECIMAL_FOR_WHOLE>1) *--p='.'; } else { p=prepend_pos_frac_digits(p,frac); *--p='.'; } if (u==0) { if (DECIMAL_FOR_WHOLE>0) *--p='0'; } else p=utoa(p,u); return p; } // modify p; return true if handled template <class F> inline bool prepend_0_etc(char *&p,F f,bool positive_sign=false) { if (f==0) { *--p='0'; return true; } if (is_nan(f)) { p-=3; p[0]='N';p[1]='A';p[2]='N'; return true; } if (is_pos_inf(f)) { p-=3; p[0]='I';p[1]='N';p[2]='F'; if (positive_sign) *--p='+'; return true; } if (is_neg_inf(f)) { p-=4; p[0]='-';p[1]='I';p[2]='N';p[3]='F'; return true; } return false; } template <class F> inline char *prepend_nonsci(char *p,F f,bool positive_sign=false) { if (prepend_0_etc(p,f,positive_sign)) return p; if (f<0) { p=prepend_pos_nonsci(p,-f); *--p='-'; } else { p=prepend_pos_nonsci(p,f); if (positive_sign) *--p='+'; } return p; } template <class F> inline char *prepend_pos_sci(char *p,F f,bool positive_sign_exp=false) { FTOAassert(f>0); typedef ftoa_traits<F> FT; int e10; F mant=FT::mantexp10(f,e10); DBFTOA(f); DBFTOA2(mant,e10); FTOAassert(mant<10.00001); if (mant>=10.) { ++e10; mant*=.1; } else if (mant < 1.) { --e10; mant*=10; } p=itoa(p,e10,positive_sign_exp); *--p='e'; return prepend_pos_nonsci(p,mant); } template <class F> inline char *prepend_sci(char *p,F f,bool positive_sign_mant=false,bool positive_sign_exp=false) { if (prepend_0_etc(p,f,positive_sign_mant)) return p; if (f==0) *--p='0'; else if (f<0) { p=prepend_pos_sci(p,-f,positive_sign_exp); *--p='-'; } else { p=prepend_pos_sci(p,f,positive_sign_exp); if (positive_sign_mant) *--p='+'; } return p; } template <class F> inline char *append_nonsci(char *p,F f,bool positive_sign=false) { if (positive_sign&&f>=0) *p++='+'; return p+ftoa_traits<F>::sprintf_nonsci(p,f); } template <class F> inline char *append_sci(char *p,F f,bool positive_sign=false) { if (positive_sign&&f>=0) *p++='+'; return p+ftoa_traits<F>::sprintf_sci(p,f); } template <class F> inline char *append_ftoa(char *p,F f,bool positive_sign=false) { if (positive_sign&&f>=0) *p++='+'; return p+ftoa_traits<F>::sprintf(p,f); } template <class F> inline char *prepend_ftoa(char *p,F f) { typedef ftoa_traits<F> FT; return FT::use_sci(f) ? prepend_sci(p,f) : prepend_nonsci(p,f); } template <class F> inline std::string ftos_append(F f) { typedef ftoa_traits<F> FT; char buf[FT::bufsize]; return std::string(buf,append_ftoa(buf,f)); } template <class F> inline std::string ftos_prepend(F f) { typedef ftoa_traits<F> FT; char buf[FT::bufsize]; char *end=buf+FT::bufsize; return std::string(prepend_ftoa(end,f),end); } template <class F> inline std::string ftos(F f) { #if 0 // trust RVO? no extra copies? return FTOA_USE_SPRINTF ? ftos_append(f) : ftos_prepend(f); #else typedef ftoa_traits<F> FT; char buf[FT::bufsize]; if (FTOA_USE_SPRINTF) { return std::string(buf,append_ftoa(buf,f)); } else { char *end=buf+FT::bufsize; return std::string(prepend_ftoa(end,f),end); } #endif } namespace { const int ftoa_bufsize=30; char ftoa_outbuf[ftoa_bufsize]; } // not even THREADLOCAL - don't use. inline char *static_ftoa(float f) { if (FTOA_USE_SPRINTF) { append_ftoa(ftoa_outbuf,f); return ftoa_outbuf; } else { char *end=ftoa_outbuf+ftoa_bufsize; return prepend_ftoa(end,f); } } #endif