diff options
Diffstat (limited to 'utils')
-rwxr-xr-x | utils/ftoa.h | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/utils/ftoa.h b/utils/ftoa.h new file mode 100755 index 00000000..81a685ac --- /dev/null +++ b/utils/ftoa.h @@ -0,0 +1,402 @@ +#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); +} + +// 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 |