diff mbox series

[libphobos] Committed IEEE quadruple support to std.conv

Message ID CABOHX+fYi+fSAxXx-uuVu6HmHxF7L+DiMCsP90r0iiWjkTCgfw@mail.gmail.com
State New
Headers show
Series [libphobos] Committed IEEE quadruple support to std.conv | expand

Commit Message

Iain Buclaw Nov. 17, 2018, 3:51 p.m. UTC
This patch is backported from phobos 2.079, in continuation of
finishing off AArch64 library support.

Bootstrapped and ran D2 testsuite on x86_64-linux-gnu.

Committed to trunk as r266238.
diff mbox series

Patch

diff --git a/libphobos/src/std/conv.d b/libphobos/src/std/conv.d
index 127849d0131..76ac5321382 100644
--- a/libphobos/src/std/conv.d
+++ b/libphobos/src/std/conv.d
@@ -2644,7 +2644,6 @@  Target parse(Target, Source)(ref Source source)
 if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) &&
     isFloatingPoint!Target && !is(Target == enum))
 {
-    import core.stdc.math : HUGE_VAL;
     import std.ascii : isDigit, isAlpha, toLower, toUpper, isHexDigit;
     import std.exception : enforce;
 
@@ -2669,7 +2668,7 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
     {
         if (msg == null)
             msg = "Floating point conversion error";
-        return new ConvException(text(msg, " for input \"", p, "\"."), fn, ln);
+        return new ConvException(text(msg, " for input \"", source, "\"."), fn, ln);
     }
 
 
@@ -2684,29 +2683,24 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
         enforce(!p.empty, bailOut());
         if (toLower(p.front) == 'i')
             goto case 'i';
-        enforce(!p.empty, bailOut());
         break;
     case '+':
         p.popFront();
         enforce(!p.empty, bailOut());
         break;
     case 'i': case 'I':
+        // inf
         p.popFront();
-        enforce(!p.empty, bailOut());
-        if (toLower(p.front) == 'n')
-        {
-            p.popFront();
-            enforce(!p.empty, bailOut());
-            if (toLower(p.front) == 'f')
-            {
-                // 'inf'
-                p.popFront();
-                static if (isNarrowString!Source)
-                    source = cast(Source) p;
-                return sign ? -Target.infinity : Target.infinity;
-            }
-        }
-        goto default;
+        enforce(!p.empty && toUpper(p.front) == 'N',
+               bailOut("error converting input to floating point"));
+        p.popFront();
+        enforce(!p.empty && toUpper(p.front) == 'F',
+               bailOut("error converting input to floating point"));
+        // skip past the last 'f'
+        p.popFront();
+        static if (isNarrowString!Source)
+            source = cast(Source) p;
+        return sign ? -Target.infinity : Target.infinity;
     default: {}
     }
 
@@ -2723,247 +2717,97 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
         }
 
         isHex = p.front == 'x' || p.front == 'X';
+        if (isHex) p.popFront();
     }
+    else if (toLower(p.front) == 'n')
+    {
+        // nan
+        p.popFront();
+        enforce(!p.empty && toUpper(p.front) == 'A',
+               bailOut("error converting input to floating point"));
+        p.popFront();
+        enforce(!p.empty && toUpper(p.front) == 'N',
+               bailOut("error converting input to floating point"));
+        // skip past the last 'n'
+        p.popFront();
+        static if (isNarrowString!Source)
+            source = cast(Source) p;
+        return typeof(return).nan;
+    }
+
+    /*
+     * The following algorithm consists of 2 steps:
+     * 1) parseDigits processes the textual input into msdec and possibly
+     *    lsdec/msscale variables, followed by the exponent parser which sets
+     *    exp below.
+     *    Hex: input is 0xaaaaa...p+000... where aaaa is the mantissa in hex
+     *    and 000 is the exponent in decimal format with base 2.
+     *    Decimal: input is 0.00333...p+000... where 0.0033 is the mantissa
+     *    in decimal and 000 is the exponent in decimal format with base 10.
+     * 2) Convert msdec/lsdec and exp into native real format
+     */
 
     real ldval = 0.0;
     char dot = 0;                        /* if decimal point has been seen */
     int exp = 0;
-    long msdec = 0, lsdec = 0;
+    ulong msdec = 0, lsdec = 0;
     ulong msscale = 1;
+    bool sawDigits;
 
-    if (isHex)
-    {
-        int guard = 0;
-        int anydigits = 0;
-        uint ndigits = 0;
-
-        p.popFront();
-        while (!p.empty)
-        {
-            int i = p.front;
-            while (isHexDigit(i))
-            {
-                anydigits = 1;
-                i = isAlpha(i) ? ((i & ~0x20) - ('A' - 10)) : i - '0';
-                if (ndigits < 16)
-                {
-                    msdec = msdec * 16 + i;
-                    if (msdec)
-                        ndigits++;
-                }
-                else if (ndigits == 16)
-                {
-                    while (msdec >= 0)
-                    {
-                        exp--;
-                        msdec <<= 1;
-                        i <<= 1;
-                        if (i & 0x10)
-                            msdec |= 1;
-                    }
-                    guard = i << 4;
-                    ndigits++;
-                    exp += 4;
-                }
-                else
-                {
-                    guard |= i;
-                    exp += 4;
-                }
-                exp -= dot;
-                p.popFront();
-                if (p.empty)
-                    break;
-                i = p.front;
-                if (i == '_')
-                {
-                    p.popFront();
-                    if (p.empty)
-                        break;
-                    i = p.front;
-                }
-            }
-            if (i == '.' && !dot)
-            {
-                p.popFront();
-                dot = 4;
-            }
-            else
-                break;
-        }
-
-        // Round up if (guard && (sticky || odd))
-        if (guard & 0x80 && (guard & 0x7F || msdec & 1))
-        {
-            msdec++;
-            if (msdec == 0)                 // overflow
-            {
-                msdec = 0x8000000000000000L;
-                exp++;
-            }
-        }
-
-        enforce(anydigits, bailOut());
-        enforce(!p.empty && (p.front == 'p' || p.front == 'P'),
-                bailOut("Floating point parsing: exponent is required"));
-        char sexp;
-        int e;
-
-        sexp = 0;
-        p.popFront();
-        if (!p.empty)
-        {
-            switch (p.front)
-            {
-                case '-':    sexp++;
-                             goto case;
-                case '+':    p.popFront(); enforce(!p.empty,
-                                new ConvException("Error converting input"~
-                                " to floating point"));
-                             break;
-                default: {}
-            }
-        }
-        ndigits = 0;
-        e = 0;
-        while (!p.empty && isDigit(p.front))
-        {
-            if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow
-            {
-                e = e * 10 + p.front - '0';
-            }
-            p.popFront();
-            ndigits = 1;
-        }
-        exp += (sexp) ? -e : e;
-        enforce(ndigits, new ConvException("Error converting input"~
-                        " to floating point"));
+    enum { hex, decimal }
 
-        static if (real.mant_dig == 64)
+    // sets msdec, lsdec/msscale, and sawDigits by parsing the mantissa digits
+    void parseDigits(alias FloatFormat)()
+    {
+        static if (FloatFormat == hex)
         {
-            if (msdec)
-            {
-                int e2 = 0x3FFF + 63;
-
-                // left justify mantissa
-                while (msdec >= 0)
-                {
-                    msdec <<= 1;
-                    e2--;
-                }
-
-                // Stuff mantissa directly into real
-                ()@trusted{ *cast(long*)&ldval = msdec; }();
-                ()@trusted{ (cast(ushort*)&ldval)[4] = cast(ushort) e2; }();
-
-                import std.math : ldexp;
-
-                // Exponent is power of 2, not power of 10
-                ldval = ldexp(ldval,exp);
-            }
+            enum uint base = 16;
+            enum ulong msscaleMax = 0x1000_0000_0000_0000UL; // largest power of 16 a ulong holds
+            enum ubyte expIter = 4; // iterate the base-2 exponent by 4 for every hex digit
+            alias checkDigit = isHexDigit;
+            /*
+             * convert letter to binary representation: First clear bit
+             * to convert lower space chars to upperspace, then -('A'-10)
+             * converts letter A to 10, letter B to 11, ...
+             */
+            alias convertDigit = (int x) => isAlpha(x) ? ((x & ~0x20) - ('A' - 10)) : x - '0';
+            sawDigits = false;
         }
-        else static if (real.mant_dig == 53)
+        else static if (FloatFormat == decimal)
         {
-            if (msdec)
-            {
-                //Exponent bias + 52:
-                //After shifting 52 times left, exp must be 1
-                int e2 = 0x3FF + 52;
-
-                // right justify mantissa
-                // first 11 bits must be zero, rest is implied bit + mantissa
-                // shift one time less, do rounding, shift again
-                while ((msdec & 0xFFC0_0000_0000_0000) != 0)
-                {
-                    msdec  = ((cast(ulong) msdec) >> 1);
-                    e2++;
-                }
-
-                //Have to shift one more time
-                //and do rounding
-                if ((msdec & 0xFFE0_0000_0000_0000) != 0)
-                {
-                    auto roundUp = (msdec & 0x1);
-
-                    msdec  = ((cast(ulong) msdec) >> 1);
-                    e2++;
-                    if (roundUp)
-                    {
-                        msdec += 1;
-                        //If mantissa was 0b1111... and we added +1
-                        //the mantissa should be 0b10000 (think of implicit bit)
-                        //and the exponent increased
-                        if ((msdec & 0x0020_0000_0000_0000) != 0)
-                        {
-                            msdec = 0x0010_0000_0000_0000;
-                            e2++;
-                        }
-                    }
-                }
-
-
-                // left justify mantissa
-                // bit 11 must be 1
-                while ((msdec & 0x0010_0000_0000_0000) == 0)
-                {
-                    msdec <<= 1;
-                    e2--;
-                }
-
-                // Stuff mantissa directly into double
-                // (first including implicit bit)
-                ()@trusted{ *cast(long *)&ldval = msdec; }();
-                //Store exponent, now overwriting implicit bit
-                ()@trusted{ *cast(long *)&ldval &= 0x000F_FFFF_FFFF_FFFF; }();
-                ()@trusted{ *cast(long *)&ldval |= ((e2 & 0xFFFUL) << 52); }();
-
-                import std.math : ldexp;
-
-                // Exponent is power of 2, not power of 10
-                ldval = ldexp(ldval,exp);
-            }
+            enum uint base = 10;
+            enum ulong msscaleMax = 10_000_000_000_000_000_000UL; // largest power of 10 a ulong holds
+            enum ubyte expIter = 1; // iterate the base-10 exponent once for every decimal digit
+            alias checkDigit = isDigit;
+            alias convertDigit = (int x) => x - '0';
+            // Used to enforce that any mantissa digits are present
+            sawDigits = startsWithZero;
         }
         else
-            static assert(false, "Floating point format of real type not supported");
-
-        goto L6;
-    }
-    else // not hex
-    {
-        if (toUpper(p.front) == 'N' && !startsWithZero)
-        {
-            // nan
-            p.popFront();
-            enforce(!p.empty && toUpper(p.front) == 'A',
-                   new ConvException("error converting input to floating point"));
-            p.popFront();
-            enforce(!p.empty && toUpper(p.front) == 'N',
-                   new ConvException("error converting input to floating point"));
-            // skip past the last 'n'
-            p.popFront();
-            static if (isNarrowString!Source)
-                source = cast(Source) p;
-            return typeof(return).nan;
-        }
-
-        bool sawDigits = startsWithZero;
+            static assert(false, "Unrecognized floating-point format used.");
 
         while (!p.empty)
         {
             int i = p.front;
-            while (isDigit(i))
+            while (checkDigit(i))
             {
                 sawDigits = true;        /* must have at least 1 digit   */
-                if (msdec < (0x7FFFFFFFFFFFL-10)/10)
-                    msdec = msdec * 10 + (i - '0');
-                else if (msscale < (0xFFFFFFFF-10)/10)
+
+                i = convertDigit(i);
+
+                if (msdec < (ulong.max - base)/base)
+                {
+                    // For base 16: Y = ... + y3*16^3 + y2*16^2 + y1*16^1 + y0*16^0
+                    msdec = msdec * base + i;
+                }
+                else if (msscale < msscaleMax)
                 {
-                    lsdec = lsdec * 10 + (i - '0');
-                    msscale *= 10;
+                    lsdec = lsdec * base + i;
+                    msscale *= base;
                 }
                 else
                 {
-                    exp++;
+                    exp += expIter;
                 }
                 exp -= dot;
                 p.popFront();
@@ -2981,21 +2825,29 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
             if (i == '.' && !dot)
             {
                 p.popFront();
-                dot++;
+                dot += expIter;
             }
             else
-            {
                 break;
-            }
         }
-        enforce(sawDigits, new ConvException("no digits seen"));
+
+        // Have we seen any mantissa digits so far?
+        enforce(sawDigits, bailOut("no digits seen"));
+        static if (FloatFormat == hex)
+            enforce(!p.empty && (p.front == 'p' || p.front == 'P'),
+                    bailOut("Floating point parsing: exponent is required"));
     }
-    if (!p.empty && (p.front == 'e' || p.front == 'E'))
+
+    if (isHex)
+        parseDigits!hex;
+    else
+        parseDigits!decimal;
+
+    if (isHex || (!p.empty && (p.front == 'e' || p.front == 'E')))
     {
-        char sexp;
-        int e;
+        char sexp = 0;
+        int e = 0;
 
-        sexp = 0;
         p.popFront();
         enforce(!p.empty, new ConvException("Unexpected end of input"));
         switch (p.front)
@@ -3006,8 +2858,7 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
                          break;
             default: {}
         }
-        bool sawDigits = 0;
-        e = 0;
+        sawDigits = false;
         while (!p.empty && isDigit(p.front))
         {
             if (e < 0x7FFFFFFF / 10 - 10)   // prevent integer overflow
@@ -3015,7 +2866,7 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
                 e = e * 10 + p.front - '0';
             }
             p.popFront();
-            sawDigits = 1;
+            sawDigits = true;
         }
         exp += (sexp) ? -e : e;
         enforce(sawDigits, new ConvException("No digits seen."));
@@ -3024,7 +2875,14 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
     ldval = msdec;
     if (msscale != 1)               /* if stuff was accumulated in lsdec */
         ldval = ldval * msscale + lsdec;
-    if (ldval)
+    if (isHex)
+    {
+        import std.math : ldexp;
+
+        // Exponent is power of 2, not power of 10
+        ldval = ldexp(ldval,exp);
+    }
+    else if (ldval)
     {
         uint u = 0;
         int pow = 4096;
@@ -3051,10 +2909,10 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
             u++;
         }
     }
-  L6: // if overflow occurred
-    enforce(ldval != HUGE_VAL, new ConvException("Range error"));
 
-  L1:
+    // if overflow occurred
+    enforce(ldval != real.infinity, new ConvException("Range error"));
+
     static if (isNarrowString!Source)
         source = cast(Source) p;
     return sign ? -ldval : ldval;
@@ -3232,15 +3090,20 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
 {
     import core.stdc.errno;
     import core.stdc.stdlib;
+    import std.math : floatTraits, RealFormat;
 
     errno = 0;  // In case it was set by another unittest in a different module.
     struct longdouble
     {
-        static if (real.mant_dig == 64)
+        static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple)
+        {
+            ushort[8] value;
+        }
+        else static if (floatTraits!real.realFormat == RealFormat.ieeeExtended)
         {
             ushort[5] value;
         }
-        else static if (real.mant_dig == 53)
+        else static if (floatTraits!real.realFormat == RealFormat.ieeeDouble)
         {
             ushort[4] value;
         }
@@ -3254,9 +3117,12 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
     longdouble x1;
     int i;
 
-    static if (real.mant_dig == 64)
+    static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple)
+        // Our parser is currently limited to ieeeExtended precision
+        enum s = "0x1.FFFFFFFFFFFFFFFEp-16382";
+    else static if (floatTraits!real.realFormat == RealFormat.ieeeExtended)
         enum s = "0x1.FFFFFFFFFFFFFFFEp-16382";
-    else static if (real.mant_dig == 53)
+    else static if (floatTraits!real.realFormat == RealFormat.ieeeDouble)
         enum s = "0x1.FFFFFFFFFFFFFFFEp-1000";
     else
         static assert(false, "Floating point format for real not supported");
@@ -3266,7 +3132,7 @@  if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum
     assert(s2.empty);
     x = *cast(longdouble *)&ld;
 
-    static if (real.mant_dig == 64)
+    static if (floatTraits!real.realFormat == RealFormat.ieeeExtended)
     {
         version (CRuntime_Microsoft)
             ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold currently mapped to strtod