Пример #1
0
        public RyuFloat64(bool sign, ulong ieeeMantissa, uint ieeeExponent)
        {
            // Subtract 2 more so that the bounds computation has 2 additional bits
            const int DOUBLE_EXP_DIFF_P2 = DOUBLE_BIAS + DOUBLE_MANTISSA_BITS + 2;
            int       exponent;
            ulong     mantissa;

            if (ieeeExponent == 0)
            {
                exponent = 1 - DOUBLE_EXP_DIFF_P2;
                mantissa = ieeeMantissa;
            }
            else
            {
                exponent = (int)ieeeExponent - DOUBLE_EXP_DIFF_P2;
                mantissa = (1UL << DOUBLE_MANTISSA_BITS) | ieeeMantissa;
            }

            // Step 2: Determine the interval of valid decimal representations
            bool even = (mantissa & 1UL) == 0UL, acceptBounds = even, mmShift =
                ieeeMantissa != 0U || ieeeExponent <= 1U;
            ulong mv = mantissa << 2;

            // Step 3: Convert to a decimal power base using 128-bit arithmetic
            ulong vr, vm, vp;
            int   e10;
            bool  vmTrailingZeroes = false, vrTrailingZeroes = false;

            if (exponent >= 0)
            {
                // Tried special-casing q == 0, but there was no effect on performance
                // This expression is slightly faster than max(0, log10Pow2(e2) - 1)
                int q = RyuUtils.Log10Pow2(exponent);
                if (exponent > 3)
                {
                    q--;
                }
                int k = DOUBLE_POW5_INV_BITCOUNT + RyuUtils.Pow5Bits(q) - 1, i = -exponent +
                                                                                 q + k;
                ulong a = RyuTables.double_computeInvPow5(q, out ulong b);
                e10 = q;
                vr  = RyuUtils.MulShiftAll(mantissa, a, b, i, out vp, out vm, mmShift);
                if (q <= 21U)
                {
                    // This should use q <= 22, but I think 21 is also safe. Smaller values
                    // may still be safe, but it's more difficult to reason about them.
                    // Only one of mp, mv, and mm can be a multiple of 5, if any
                    uint mvMod5 = (uint)mv % 5U;
                    if (mvMod5 == 0U)
                    {
                        vrTrailingZeroes = RyuUtils.IsMultipleOf5Power(mv, q);
                    }
                    else if (acceptBounds)
                    {
                        // Same as min(e2 + (~mm & 1), pow5Factor(mm)) >= q
                        // <=> e2 + (~mm & 1) >= q && pow5Factor(mm) >= q
                        // <=> true && pow5Factor(mm) >= q, since e2 >= q
                        vmTrailingZeroes = RyuUtils.IsMultipleOf5Power(mv - 1UL - (mmShift ?
                                                                                   1UL : 0UL), q);
                    }
                    else
                    {
                        // Same as min(e2 + 1, pow5Factor(mp)) >= q
                        vp -= RyuUtils.IsMultipleOf5Power(mv + 2UL, q) ? 1UL : 0UL;
                    }
                }
            }
            else
            {
                // This expression is slightly faster than max(0, log10Pow5(-e2) - 1)
                int q = RyuUtils.Log10Pow5(-exponent);
                if (-exponent > 1)
                {
                    q--;
                }
                int i = -exponent - q, k = RyuUtils.Pow5Bits(i) - DOUBLE_POW5_BITCOUNT,
                    j   = q - k;
                ulong a = RyuTables.double_computePow5(i, out ulong b);
                e10 = q + exponent;
                vr  = RyuUtils.MulShiftAll(mantissa, a, b, j, out vp, out vm, mmShift);
                if (q <= 1U)
                {
                    // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0
                    // bits; mv = 4 * m2, so it always has at least two trailing 0 bits
                    vrTrailingZeroes = true;
                    if (acceptBounds)
                    {
                        // mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1
                        vmTrailingZeroes = mmShift;
                    }
                    else
                    {
                        // mp = mv + 2, so it always has at least one trailing 0 bit
                        --vp;
                    }
                }
                else if (q < 63U)
                {
                    // We want to know if the fUL product has at least q trailing zeros
                    // We need to compute min(p2(mv), p5(mv) - e2) >= q
                    // <=> p2(mv) >= q && p5(mv) - e2 >= q
                    // <=> p2(mv) >= q (because -e2 >= q)
                    vrTrailingZeroes = RyuUtils.IsMultipleOf2Power(mv, q);
                }
            }

            // Step 4: Find the shortest decimal representation in the interval of valid
            // representations
            int   removed = 0;
            uint  removedDigit = 0U;
            ulong output, p10, m10;

            // On average, we remove ~2 digits
            if (vmTrailingZeroes || vrTrailingZeroes)
            {
                // General case, which happens rarely (~0.7%)
                while ((p10 = vp / 10UL) > (m10 = vm.DivMod(10U, out uint vmRem)))
                {
                    if (vmRem != 0U)
                    {
                        vmTrailingZeroes = false;
                    }
                    if (removedDigit != 0U)
                    {
                        vrTrailingZeroes = false;
                    }
                    vr = vr.DivMod(10U, out removedDigit);
                    vp = p10;
                    vm = m10;
                    removed++;
                }
                if (vmTrailingZeroes)
                {
                    while (((uint)vm - 10U * (uint)(m10 = vm / 10UL)) == 0U)
                    {
                        if (removedDigit != 0U)
                        {
                            vrTrailingZeroes = false;
                        }
                        vr  = vr.DivMod(10U, out removedDigit);
                        vp /= 10UL;
                        vm  = m10;
                        removed++;
                    }
                }
                if (vrTrailingZeroes && removedDigit == 5U && vr % 2U == 0U)
                {
                    // Round even if the exact number is .....50..0
                    removedDigit = 4U;
                }
                // We need to take vr + 1 if vr is outside bounds or we need to round up
                output = vr;
                if ((vr == vm && (!acceptBounds || !vmTrailingZeroes)) || removedDigit >= 5U)
                {
                    output++;
                }
            }
            else
            {
                // Specialized for the common case (~99.3%); percentages below are relative to
                // this
                bool roundUp = false;
                if (RyuUtils.DivCompare(ref vp, ref vm, 100UL))
                {
                    // Optimization: remove two digits at a time (~86.2%)
                    vr       = vr.DivMod(100U, out uint round100);
                    roundUp  = round100 >= 50U;
                    removed += 2;
                }
                // Loop iterations below (approximately), without optimization above:
                // 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02%
                // Loop iterations below (approximately), with optimization above:
                // 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02%
                while (RyuUtils.DivCompare(ref vp, ref vm, 10UL))
                {
                    vr      = vr.DivMod(10U, out uint vrRem);
                    roundUp = vrRem >= 5U;
                    removed++;
                }
                // We need to take vr + 1 if vr is outside bounds or we need to round up
                output = vr;
                if (vr == vm || roundUp)
                {
                    output++;
                }
            }

            this.sign     = sign;
            this.exponent = e10 + removed;
            this.mantissa = output;
        }