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; }