www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Requesting a final review: on the floating-point funcs for conv.d

Well, first off I'd like to give many thanks to Ben Hinkle for his reviews! I
believe, I've improved this code to a point that I feel some pride in having
spend so much time in writing it. (Now, I just hope Walter won't wait six months
to put it in, tho I know he's busy man.)

Changes done since last review:
-------------------------------
* deleted the getFloatStrings() function which did double-checks on strings
before they were sended into the std.math2.atof() function, mainly because it
would return zeros even when the string should've been a error. Plus I decided
that since Walter was spenting some of his time improving the std.string.atof()
function that maybe I should switch to it. But I didn't discovered until this
weekend that the std.string.atof() was able to catch unwanted string instead of
return zeros. So, all good! :)

* added back in a modified version of the getComplexStrings() function, which is
only called for cfloat, cdouble, and creal datatypes. With the sole purpose of
just spliting up the orginal string into two real string values for processing
thru the strtold() function.

* now instead of calling std.string.atof(), the code is setup to call the
strtold() function which atof() would've called. Also I'm passing a pointer into
the strtold() function pointing to the string's the last character, thus saving
any string duping that would've been going on in toStringz() function if atof()
had been used.

* now instead using down-casting a float / double and comparing against the real
result within the feq() function to catch overflows. I've changed to using a
more logic approach of "if (f > float.max) goto Loverflow;"...which works much
better. Currently complex floats (cfloat, cdouble, and creal) have a bug when
comparing against their .max value...inwhich I did report this problem on the
D.Bugs forum.

* trim out the bit deep-equate compares (i.e. if (b == 0) goto Loverflow;) and
changed them all to negative-state compares (i.e. if (!b) goto Loverflow;).

* fine tuned the unittest a little bit more.

* removed a few unnecessary cast() here and there.

* also, I'm now only importing std.math fabs() and isnan() functions, as well
as, only using the std.string.toString() functions in unittests. And the private
feq() functions are now only used in unittest, and wrapped with a "debug(conv) {
}" statement. There's much less overhead this way. :)
----------------------------------------------------

# //*** Inserted code below debug=conv;***
# debug( conv ) private import std.math;  // for fabs(), isnan()
# private import std.string;  // for atof(), toString()
# debug( conv ) private import std.stdio; // for writefln() and printf()
# //*** End of insert ***

# //*** Inserted code at the end of the conv.d file ***
# 
# /***************************************************************
#  * Convert character string to float.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# float toFloat(in char[] s)
# {
#     real r;
# 
#     if (!s.length)
#         goto Lerr;
# 
#     r = toReal(s);
# 
#     if (r > float.max)
#         goto Loverflow;
#          
#     return cast(float)r;
#     
#     Loverflow:
#         conv_overflow(s);
#         
#     Lerr:
#         conv_error(s);
#         return 0.0f;    
# }
#  
# unittest
# {
#     debug( conv ) writefln( "conv.toFloat.unittest" );
#     float f;
#     
#     f = toFloat( "123" );
#     assert( f == 123f );
#     f = toFloat( "+123" );
#     assert( f == +123f );
#     f = toFloat( "-123" );
#     assert( f == -123f );
#     f = toFloat( "123e+2" );
#     assert( f == 123e+2f );
# 
#     f = toFloat( "123e-2" );
#     assert( f == 123e-2f );
#     f = toFloat( "123." );
#     assert( f == 123.f );
#     f = toFloat( ".456" );
#     assert( f == .456f );
#     
#     // min and max
#     f = toFloat("1.17549e-38");
#     assert(feq(cast(real)f, cast(real)1.17549e-38));
#     assert(feq(cast(real)f, cast(real)float.min));
#     f = toFloat("3.40282e+38");
#     assert(toString(f) == toString(3.40282e+38));
# 
#     // nan
#     f = toFloat("nan");
#     assert(toString(f) == toString(float.nan));
# }
# 
# /***************************************************************
#  * Convert character string to double.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# double toDouble(in char[] s)
# {
#     real r;
# 
#     if (!s.length)
#         goto Lerr;
# 
#     r = toReal(s);
# 
#     if (r > double.max)
#         goto Loverflow;
#     
#     return cast(double)r;
#     
#     Loverflow:
#         conv_overflow(s);
#         
#     Lerr:
#         conv_error(s);
#         return 0.0;    
# }
# 
# unittest
# {
#     debug( conv ) writefln( "conv.toDouble.unittest" );
#     double d;
# 
#     d = toDouble( "123" );
#     assert( d == 123 );
#     d = toDouble( "+123" );
#     assert( d == +123 );
#     d = toDouble( "-123" );
#     assert( d == -123 );
#     d = toDouble( "123e2" );
#     assert( d == 123e2);
#     d = toDouble( "123e-2" );
#     assert( d == 123e-2 );
#     d = toDouble( "123." );
#     assert( d == 123. );
#     d = toDouble( ".456" );
#     assert( d == .456 );
#     d = toDouble( "1.23456E+2" );
#     assert( d == 1.23456E+2 );
# 
#     // min and max
#     d = toDouble("2.22507e-308");
#     assert(feq(cast(real)d, cast(real)2.22507e-308));
#     assert(feq(cast(real)d, cast(real)double.min));
#     d = toDouble("1.79769e+308");
#     assert(toString(d) == toString(1.79769e+308));
#     assert(toString(d) == toString(double.max));
# 
#     // nan
#     d = toDouble("nan");
#     assert(toString(d) == toString(double.nan));
#     //assert(cast(real)d == cast(real)double.nan);
# }
# 
# /***************************************************************
#  * Convert character string to real.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# real toReal(in char[] s)
# {
#     real  r;
#     char* endptr;
# 
#     if (!s.length)
#         goto Lerr;
#     
#     // atof(s);
#     endptr = &s[s.length - 1];
#     r = strtold(s, &endptr);
#     return r;
#     
#     Lerr:
#         conv_error(s);
#         return 0.0L;    
# }
# 
# unittest
# {
#     debug(conv) writefln("conv.toReal.unittest");
#     real r;
# 
#     r = toReal("123");
#     assert(r == 123L);
#     r = toReal("+123");
#     assert(r == 123L);
#     r = toReal("-123");
#     assert(r == -123L);
#     r = toReal("123e2");
#     assert(feq(r, 123e2L));
#     r = toReal("123e-2");
#     assert(feq(r, 1.23L));
#     r = toReal("123.");
#     assert(r == 123L);
#     r = toReal(".456");
#     assert(r == .456L);
# 
#     r = toReal("1.23456e+2");
#     assert(feq(r,  1.23456e+2L));
#     r = toReal(toString(real.max / 2L));
#     assert(toString(r) == toString(real.max / 2L));
# 
#     // min and max
#     r = toReal(toString(real.min));
#     assert(toString(r) == toString(real.min));
#     r = toReal(toString(real.max));
#     assert(toString(r) == toString(real.max));
# 
#     // nan
#     r = toReal("nan");
#     assert(toString(r) == toString(real.nan));
#     //assert(r == real.nan);
# 
#     r = toReal(toString(real.nan));
#     assert(toString(r) == toString(real.nan));
#     //assert(r == real.nan);
# }
# 
# /***************************************************************
#  * Convert character string to ifloat.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# ifloat toIfloat(in char[] s)
# {
#     real r;
# 
#     if (!s.length)
#         goto Lerr;
#  
#     r = toReal(s);
# 
#     if (cast(ifloat)(r * 1.0i) > ifloat.max)
#         goto Loverflow;
#     
#     return cast(ifloat)(r * 1.0i);
# 
#     Loverflow:
#         conv_overflow(s);
#         
#     Lerr:
#         conv_error(s);
#         return cast(ifloat)0.0i;  
# }
# 
# unittest
# {
#     debug(conv) writefln("conv.toIfloat.unittest");
#     ifloat ift;
#     
#     ift = toIfloat(toString(123.45));
#     assert(toString(ift) == toString(123.45i));
# 
#     ift = toIfloat(toString(456.77i));
#     assert(toString(ift) == toString(456.77i));
# 
#     // min and max
#     ift = toIfloat(toString(ifloat.min));
#     assert(toString(ift) == toString(ifloat.min) );
#     assert(feq(cast(ireal)ift, cast(ireal)ifloat.min));
# 
#     ift = toIfloat(toString(ifloat.max));
#     assert(toString(ift) == toString(ifloat.max));
#     assert(feq(cast(ireal)ift, cast(ireal)ifloat.max));
#    
#     // nan
#     ift = toIfloat("nani");
#     assert(cast(real)ift == cast(real)ifloat.nan);
# 
#     ift = toIfloat(toString(ifloat.nan));
#     assert(toString(ift) == toString(ifloat.nan));
#     assert(feq(cast(ireal)ift, cast(ireal)ifloat.nan));
# }
# 
# /***************************************************************
#  * Convert character string to idouble.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# idouble toIdouble(in char[] s)
# {
#     real r;
# 
#     if (!s.length)
#         goto Lerr;
# 
#     r = toReal(s);
# 
#     if (cast(idouble)(r * 1.0i) > ireal.max)
#         goto Loverflow;
#     
#     return cast(idouble)(r * 1.0i); 
# 
#     Loverflow:
#         conv_overflow(s);
#         
#     Lerr:
#         conv_error(s);
#         return cast(idouble)0.0i;  
# }
# 
# unittest
# {
#     debug(conv) writefln("conv.toIdouble.unittest");
#     idouble id;
# 
#     id = toIdouble(toString("123.45"));
#     assert(id == 123.45i);
# 
#     id = toIdouble(toString("123.45e+302i"));
#     assert(id == 123.45e+302i);
# 
#     // min and max
#     id = toIdouble(toString(idouble.min));
#     assert(toString( id ) == toString(idouble.min));
#     assert(feq(cast(ireal)id.re, cast(ireal)idouble.min.re));
#     assert(feq(cast(ireal)id.im, cast(ireal)idouble.min.im));
#     
#     id = toIdouble(toString(idouble.max));
#     assert(toString(id) == toString(idouble.max));
#     assert(feq(cast(ireal)id.re, cast(ireal)idouble.max.re));
#     assert(feq(cast(ireal)id.im, cast(ireal)idouble.max.im));
#     
#     // nan
#     id = toIdouble("nani");
#     assert(cast(real)id == cast(real)idouble.nan);
# 
#     id = toIdouble(toString(idouble.nan));
#     assert(toString(id) == toString(idouble.nan));
# }
# 
# /***************************************************************
#  * Convert character string to ireal.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# ireal toIreal(in char[] s)
# {
#     real  r;
#     ireal ir;
# 
#     if (!s.length)
#         goto Lerr;
#  
#     r = toReal(s);
#  
#     ir = cast(ireal)(r * 1.0i);
# 
#     return ir;
#     
#     Lerr:
#         conv_error(s);
#         return cast(ireal)0.0i;  
# }
# 
# unittest
# {
#     debug(conv) writefln("conv.toIreal.unittest");
#     ireal ir;
# 
#     ir = toIreal(toString("123.45"));
#     assert(feq(cast(real)ir.re, cast(real)123.45i)); 
# 
#     ir = toIreal(toString("123.45e+82i"));
#     assert(toString(ir) == toString(123.45e+82i));
#     //assert(ir == 123.45e+82i);
# 
#     // min and max
#     ir = toIreal(toString(ireal.min));
#     assert(toString(ir) == toString(ireal.min));
#     assert(feq(cast(real)ir.re, cast(real)ireal.min.re));
#     assert(feq(cast(real)ir.im, cast(real)ireal.min.im));
# 
#     ir = toIreal(toString(ireal.max));
#     assert(toString(ir) == toString(ireal.max));
#     assert(feq(cast(real)ir.re, cast(real)ireal.max.re));
#     //assert(feq(cast(real)ir.im, cast(real)ireal.max.im));
# 
#     // nan
#     ir = toIreal("nani");
#     assert(cast(real)ir == cast(real)ireal.nan);
# 
#     ir = toIreal(toString(ireal.nan));
#     assert(toString(ir) == toString(ireal.nan));
# }
# 
# /***************************************************************
#  * Convert character string to cfloat.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# cfloat toCfloat(in char[] s)
# {
#     char[] s1;
#     char[] s2;
#     real   r1;
#     real   r2;
#     cfloat cf;
#     bit    b = 0;
#     char*  endptr;
# 
#     if (!s.length)
#         goto Lerr;
#     
#     b = getComplexStrings(s, s1, s2);
# 
#     if (!b)
#         goto Lerr;
#     
#     // atof(s1);
#     endptr = &s1[s1.length - 1];
#     r1 = strtold(s1, &endptr); 
# 
#     // atof(s2);
#     endptr = &s2[s2.length - 1];
#     r2 = strtold(s2, &endptr); 
# 
#     cf = cast(cfloat)(r1 + (r2 * 1.0i));
# 
#     //writefln( "toCfloat() r1=%g, r2=%g, cf=%g, max=%g", 
#     //           r1, r2, cf, cfloat.max);
#     // Currently disabled due to a posted bug where a 
#     // complex float greater-than compare to .max compares 
#     // incorrectly.
#     //if (cf > cfloat.max)
#     //    goto Loverflow;
# 
#     return cf;
# 
#     Loverflow:
#         conv_overflow(s);
#         
#     Lerr:
#         conv_error(s);
#         return cast(cfloat)0.0e-0+0i;   
# }
# 
# unittest
# {
#     debug(conv) writefln("conv.toCfloat.unittest");
#     cfloat cf;
# 
#     cf = toCfloat(toString("1.2345e-5+0i"));
#     assert(toString(cf) == toString(1.2345e-5+0i));
#     assert(feq(cf, 1.2345e-5+0i));
# 
#     // min and max
#     cf = toCfloat(toString(cfloat.min));
#     assert(toString(cf) == toString(cfloat.min));
# 
#     cf = toCfloat(toString(cfloat.max));
#     assert(toString(cf) == toString(cfloat.max));
#    
#     // nan ( nan+nani )
#     cf = toCfloat("nani");
#     //writefln("toCfloat() cf=%g, cf=\"%s\", nan=%s", 
#     //         cf, toString(cf), toString(cfloat.nan));
#     assert(toString(cf) == toString(cfloat.nan));
# 
#     cf = toCdouble("nan+nani");
#     assert(toString(cf) == toString(cfloat.nan));
# 
#     cf = toCfloat(toString(cfloat.nan));
#     assert(toString(cf) == toString(cfloat.nan));
#     assert(feq(cast(creal)cf, cast(creal)cfloat.nan));
# }
# 
# /***************************************************************
#  * Convert character string to cdouble.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# cdouble toCdouble(in char[] s)
# {
#     char[]  s1;
#     char[]  s2;
#     real    r1;
#     real    r2;
#     cdouble cd;
#     bit     b = 0;
#     char*   endptr;
# 
#     if (!s.length)
#         goto Lerr;
#     
#     b = getComplexStrings(s, s1, s2);
# 
#     if (!b)
#         goto Lerr;
# 
#     // atof(s1);
#     endptr = &s1[s1.length - 1];
#     r1 = strtold(s1, &endptr); 
# 
#     // atof(s2);
#     endptr = &s2[s2.length - 1];
#     r2 = strtold(s2, &endptr); //atof(s2);
# 
#     cd = cast(cdouble)(r1 + (r2 * 1.0i));
#  
#     //Disabled, waiting on a bug fix.
#     //if (cd > cdouble.max)  //same problem the toCfloat() having
#     //    goto Loverflow;
# 
#     return cd;
# 
#     Loverflow:
#         conv_overflow(s);
#         
#     Lerr:
#         conv_error(s);
#         return cast(cdouble)0.0e-0+0i; 
# }
# 
# unittest
# {
#     debug(conv) writefln("conv.toCdouble.unittest");
#     cdouble cd;
# 
#     cd = toCdouble(toString("1.2345e-5+0i"));
#     assert(toString( cd ) == toString(1.2345e-5+0i));
#     assert(feq(cd, 1.2345e-5+0i));
# 
#     // min and max
#     cd = toCdouble(toString(cdouble.min));
#     assert(toString(cd) == toString(cdouble.min));
#     assert(feq(cast(creal)cd, cast(creal)cdouble.min));
# 
#     cd = toCdouble(toString(cdouble.max));
#     assert(toString( cd ) == toString(cdouble.max));
#     assert(feq(cast(creal)cd, cast(creal)cdouble.max));
# 
#     // nan ( nan+nani )
#     cd = toCdouble("nani");
#     assert(toString(cd) == toString(cdouble.nan));
# 
#     cd = toCdouble("nan+nani");
#     assert(toString(cd) == toString(cdouble.nan));
# 
#     cd = toCdouble(toString(cdouble.nan));
#     assert(toString(cd) == toString(cdouble.nan));
#     assert(feq(cast(creal)cd, cast(creal)cdouble.nan));
# }
# 
# /***************************************************************
#  * Convert character string to creal.
#  * Grammar:
#  * ['+'|'-'] digit {digit}
#  */
# creal toCreal(in char[] s)
# {
#     char[] s1;
#     char[] s2;
#     real   r1;
#     real   r2;
#     creal  cr;
#     bit    b = 0;
#     char*  endptr;
# 
#     if (!s.length)
#         goto Lerr;
# 
#     b = getComplexStrings(s, s1, s2);
# 
#     if (!b)
#         goto Lerr;
#  
#     // atof(s1);
#     endptr = &s1[s1.length - 1];
#     r1 = strtold(s1, &endptr); 
# 
#     // atof(s2);
#     endptr = &s2[s2.length - 1];
#     r2 = strtold(s2, &endptr); //atof(s2);
# 
#     //writefln("toCreal() r1=%g, r2=%g, s1=\"%s\", s2=\"%s\", nan=%g", 
#     //          r1, r2, s1, s2, creal.nan);
#    
#     if (s1 =="nan" && s2 == "nani")
#         cr = creal.nan;
#     else if (r2 != 0.0)
#         cr = cast(creal)(r1 + (r2 * 1.0i));
#     else
#         cr = cast(creal)(r1 + 0.0i);    
#     
#     return cr;
# 
#     Lerr:
#         conv_error(s);
#         return cast(creal)0.0e-0+0i;    
# }
# 
# unittest
# {
#     debug(conv) writefln("conv.toCreal.unittest");
#     creal cr;
# 
#     cr = toCreal(toString("1.2345e-5+0i"));
#     assert(toString(cr) == toString(1.2345e-5+0i));
#     assert(feq(cr, 1.2345e-5+0i));
# 
#     cr = toCreal(toString("0.0e-0+0i"));
#     assert(toString(cr) == toString(0.0e-0+0i));
#     assert(cr == 0.0e-0+0i);
#     assert(feq(cr, 0.0e-0+0i));
#     
#     cr = toCreal("123");
#     assert(cr == 123);
# 
#     cr = toCreal("+5");
#     assert(cr == 5);
#  
#     cr = toCreal("-78");
#     assert(cr == -78);
# 
#     // min and max
#     cr = toCreal(toString(creal.min));
#     assert(toString(cr) == toString(creal.min));
#     assert(feq(cr, creal.min));
#     
#     cr = toCreal(toString(creal.max));
#     assert(toString(cr) == toString(creal.max));
#     assert(feq(cr, creal.max));
# 
#     // nan ( nan+nani )
#     cr = toCreal("nani");
#     assert(toString(cr) == toString(creal.nan));
# 
#     cr = toCreal("nan+nani");
#     assert(toString(cr) == toString(creal.nan));
# 
#     cr = toCreal(toString(cdouble.nan));
#     assert(toString(cr) == toString(creal.nan));
#     assert(feq(cr, creal.nan));
# }
# 
# /***************************************************************
#  * Splits a complex float (cfloat, cdouble, and creal) into two workable
strings.
#  * Grammar:
#  * ['+'|'-'] string floating-point digit {digit}
#  */
# private bit getComplexStrings(in char[] s, out char[] s1, out char[] s2)
# {
#     int len = s.length;
# 
#     if (!len) 
#         goto Lerr;
# 
#     // When "nan" or "nani" just return them.
#     if (s == "nan" || s == "nani" || s == "nan+nani")
#     {
#         s1 = "nan";
#         s2 = "nani";
#         return 1;
#     }
#     
#     // Split the original string out into two strings.
#     for (int i = 1; i < len; i++)
#         if ((s[i - 1] != 'e' && s[i - 1] != 'E') && s[i] == '+')
#         {
#             s1 = s[0..i];
#             if (i + 1 < len - 1)
#                 s2 = s[i + 1..len - 1];
#             else 
#                 s2 = "0e+0i";
#             
#             break;
#         }   
# 
#     // Handle the case when there's only a single value 
#     // to work with, and set the other string to zero.
#     if (!s1.length)
#     {
#         s1 = s;
#         s2 = "0e+0i";
#     }
#  
#     //writefln( "getComplexStrings() s=\"%s\", s1=\"%s\", s2=\"%s\", len=%d", 
#     //           s, s1, s2, len );
#    
#     return 1;
# 
#     Lerr:
#         // Display the original string in the error message.
#         conv_error("getComplexStrings() \"" ~ s ~ "\"" ~ " s1=\"" ~ s1 ~ "\""
~ " s2=\"" ~ s2 ~ "\"");
#         return 0;
# }
# 
# // feq() functions now used only in unittesting
# debug(conv)
# {
# /****************************************
#  * Main function to compare reals with given precision
#  */
# private bit feq(in real rx, in real ry, in real precision)
# {
#     if (rx == ry)
#         return 1;
#     
#     if (isnan(rx))
#         return cast(bit)isnan(ry);
# 
#     if (isnan(ry))
#         return 0;
#        
#     return cast(bit)(fabs(rx - ry) <= precision);
# }
# 
# /****************************************
#  * (Note: Copied here from std.math's mfeq() function for unittesting)
#  * Simple function to compare two floating point values
#  * to a specified precision.
#  * Returns:
#  *  1   match
#  *  0   nomatch
#  */
#  private bit feq(in real r1, in real r2)
# {
#     if (r1 == r2)
#         return 1;
#     
#     if (isnan(r1))
#         return cast(bit)isnan(r2);
# 
#     if (isnan(r2))
#         return 0;
#         
#     return cast(bit)(feq(r1, r2, 0.000001L));
# } 
#  
# /****************************************
#  * compare ireals with given precision
#  */
# private bit feq(in ireal r1, in ireal r2)
# {
#     real rx = cast(real)r1;
#     real ry = cast(real)r2;
# 
#     if (rx == ry)
#         return 1;
#     
#     if (isnan(rx)) 
#         return cast(bit)isnan(ry);
# 
#     if (isnan(ry))
#         return 0;
#     
#     return feq(rx, ry, 0.000001L);
# } 
# 
# /****************************************
#  * compare creals with given precision
#  */
# private bit feq(in creal r1, in creal r2)
# {
#     real r1a = fabs(cast(real)r1.re - cast(real)r2.re);
#     real r2b = fabs(cast(real)r1.im - cast(real)r2.im);
# 
#     if ((cast(real)r1.re == cast(real)r2.re) &&
#         (cast(real)r1.im == cast(real)r2.im))
#         return 1;
#     
#     if (isnan(r1a))
#         return cast(bit)isnan(r2b);
# 
#     if (isnan(r2b))
#         return 0;
# 
#     return feq(r1a, r2b, 0.000001L);
# }
# }
# //*** End of insert ***

Again many, many thanks to Ben for spenting some time reviewing this code
multiple times.

David L.

-------------------------------------------------------------------
"Dare to reach for the Stars...Dare to Dream, Build, and Achieve!"
-------------------------------------------------------------------

MKoD: http://spottedtiger.tripod.com/D_Language/D_Main_XP.html
Apr 23 2005