コード例 #1
0
        public static void GetBytes(ref BufferWriter writer, double value)
        {
            var resultBuilder = StringBuilder.Create();

            try
            {
                var doubleValue = new IeeeDouble(value);
                if (doubleValue.IsSpecial())
                {
                    if (!HandleSpecialValues(doubleValue, ref resultBuilder))
                    {
                        throw new InvalidOperationException("not support special double value:" + value);
                    }
                }
                else if (!ToShortestIeeeNumber(value, ref resultBuilder, DtoaMode.Shortest))
                {
                    throw new InvalidOperationException("not support double value:" + value);
                }

                var resultBuilderLength = resultBuilder.Length;
                var destination         = writer.GetSpan(resultBuilderLength);
                resultBuilder.ReadableSpan.CopyTo(destination);
                writer.Advance(resultBuilderLength);
            }
            finally
            {
                resultBuilder.Dispose();
            }
        }
コード例 #2
0
        // Returns true if the guess is the correct double.
        // Returns false, when guess is either correct or the next-lower double.
        private static bool ComputeGuess(ReadOnlySpan <byte> trimmed, int exponent, out double guess)
        {
            if (trimmed.Length == 0)
            {
                guess = default;
                return(true);
            }

            if (exponent + trimmed.Length - 1 >= KMaxDecimalPower)
            {
                guess = IeeeDouble.Infinity();
                return(true);
            }

            if (exponent + trimmed.Length <= KMinDecimalPower)
            {
                guess = default;
                return(true);
            }

            if (DoubleStrToDouble(trimmed, exponent, out guess) || DiyFpStrToDouble(trimmed, exponent, out guess))
            {
                return(true);
            }

            return(guess == IeeeDouble.Infinity());
        }
コード例 #3
0
        // Provides a decimal representation of v.
        // Returns true if it succeeds, otherwise the result cannot be trusted.
        // There will be *length digits inside the buffer (not null-terminated).
        // If the function returns true then
        //        v == (double) (buffer * 10^decimal_exponent).
        // The digits in the buffer are the shortest representation possible: no
        // 0.09999999999999999 instead of 0.1. The shorter representation will even be
        // chosen even if the longer one would be closer to v.
        // The last digit will be closest to the actual v. That is, even if several
        // digits might correctly yield 'v' when read again, the closest will be
        // computed.
        private static bool Grisu3(double v, FastDtoaMode mode, Span <byte> buffer, out int length, out int decimalExponent)
        {
            var w = new IeeeDouble(v).AsNormalizedDiyFp();
            // boundary_minus and boundary_plus are the boundaries between v and its
            // closest floating-point neighbors. Any number strictly between
            // boundary_minus and boundary_plus will round to v when convert to a double.
            // Grisu3 will never output representations that lie exactly on a boundary.
            DiyFp boundaryMinus, boundaryPlus;

            switch (mode)
            {
            case FastDtoaMode.FastDtoaShortest:
                new IeeeDouble(v).NormalizedBoundaries(out boundaryMinus, out boundaryPlus);
                break;

            case FastDtoaMode.FastDtoaShortestSingle:
            {
                var singleV = (float)v;
                new IeeeSingle(singleV).NormalizedBoundaries(out boundaryMinus, out boundaryPlus);
                break;
            }

            default:
                throw new Exception("Invalid Mode.");
            }

            var tenMkMinimalBinaryExponent = KMinimalTargetExponent - (w.e + DiyFp.kSignificandSize);

            PowersOfTenCache.GetCachedPowerForBinaryExponentRange(tenMkMinimalBinaryExponent, out var tenMk, out var mk);

            // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
            // 64 bit significand and ten_mk is thus only precise up to 64 bits.

            // The DiyFp::Times procedure rounds its result, and ten_mk is approximated
            // too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
            // off by a small amount.
            // In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
            // In other words: let f = scaled_w.f() and e = scaled_w.e(), then
            //           (f-1) * 2^e < w*10^k < (f+1) * 2^e
            var scaledW = DiyFp.Times(ref w, ref tenMk);

            // In theory it would be possible to avoid some recomputations by computing
            // the difference between w and boundary_minus/plus (a power of 2) and to
            // compute scaled_boundary_minus/plus by subtracting/adding from
            // scaled_w. However the code becomes much less readable and the speed
            // enhancements are not terrific.
            var scaledBoundaryMinus = DiyFp.Times(ref boundaryMinus, ref tenMk);
            var scaledBoundaryPlus  = DiyFp.Times(ref boundaryPlus, ref tenMk);

            // DigitGen will generate the digits of scaled_w. Therefore we have
            // v == (double) (scaled_w * 10^-mk).
            // Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an
            // integer than it will be updated. For instance if scaled_w == 1.23 then
            // the buffer will be filled with "123" und the decimal_exponent will be
            // decreased by 2.
            var result = DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus,
                                  buffer, out length, out var kappa);

            decimalExponent = -mk + kappa;
            return(result);
        }
コード例 #4
0
        public static bool TryParseToFloat(ReadOnlySpan <byte> buffer, int exponent, out float value)
        {
            Span <byte> copyBuffer = stackalloc byte[KMaxSignificantDecimalDigits];

            var    leftTrimmed  = TrimLeadingZeros(buffer);
            var    rightTrimmed = TrimTrailingZeros(leftTrimmed);
            var    shouldCut    = rightTrimmed.Length > KMaxSignificantDecimalDigits;
            bool   isCorrect;
            double doubleValue;

            var exponent1 = exponent + leftTrimmed.Length - rightTrimmed.Length;

            if (shouldCut)
            {
                CutToMaxSignificantDigits(rightTrimmed, exponent1, copyBuffer, out exponent);
                isCorrect = ComputeGuess(copyBuffer, exponent, out doubleValue);
            }
            else
            {
                isCorrect = ComputeGuess(rightTrimmed, exponent1, out doubleValue);
            }

            value = (float)doubleValue;
            if (value == doubleValue)
            {
                // This shortcut triggers for integer values.
                return(true);
            }

            // We must catch double-rounding. Say the double has been rounded up, and is
            // now a boundary of a float, and rounds up again. This is why we have to
            // look at previous too.
            // Example (in decimal numbers):
            //    input: 12349
            //    high-precision (4 digits): 1235
            //    low-precision (3 digits):
            //       when read from input: 123
            //       when rounded from high precision: 124.
            // To do this we simply look at the neigbors of the correct result and see
            // if they would round to the same float. If the guess is not correct we have
            // to look at four values (since two different doubles could be the correct
            // double).

            var doubleNext     = new IeeeDouble(doubleValue).NextDouble();
            var doublePrevious = new IeeeDouble(doubleValue).PreviousDouble();

            var   f1 = (float)doublePrevious;
            var   f3 = (float)doubleNext;
            float f4;

            if (isCorrect)
            {
                f4 = f3;
            }
            else
            {
                var doubleNext2 = new IeeeDouble(doubleNext).NextDouble();
                f4 = (float)doubleNext2;
            }

            // If the guess doesn't lie near a single-precision boundary we can simply
            // return its float-value.
            return(f1 == f4);
        }
コード例 #5
0
        // If the function returns true then the result is the correct double.
        // Otherwise it is either the correct double or the double that is just below
        // the correct double.
        private static bool DiyFpStrToDouble(ReadOnlySpan <byte> buffer, int exponent, out double result)
        {
            ReadDiyFp(buffer, out var input, out var remainingDecimals);
            // Since we may have dropped some digits the input is not accurate.
            // If remaining_decimals is different than 0 than the error is at most
            // .5 ulp (unit in the last place).
            // We don't want to deal with fractions and therefore keep a common
            // denominator.
            const int kDenominatorLog = 3;
            const int kDenominator    = 1 << kDenominatorLog;

            // Move the remaining decimals into the exponent.
            exponent += remainingDecimals;
            var error = (ulong)(remainingDecimals == 0 ? 0 : kDenominator / 2);

            var oldE = input.e;

            input.Normalize();
            error <<= oldE - input.e;

            if (exponent < PowersOfTenCache.kMinDecimalExponent)
            {
                result = 0.0;
                return(true);
            }

            PowersOfTenCache.GetCachedPowerForDecimalExponent(exponent, out var cachedPower, out var cachedDecimalExponent);

            if (cachedDecimalExponent != exponent)
            {
                var adjustmentExponent = exponent - cachedDecimalExponent;
                var adjustmentPower    = AdjustmentPowerOfTen(adjustmentExponent);
                input.Multiply(ref adjustmentPower);
                if (KMaxUint64DecimalDigits - buffer.Length >= adjustmentExponent)
                {
                    // The product of input with the adjustment power fits into a 64 bit
                    // integer.
                }
                else
                {
                    // The adjustment power is exact. There is hence only an error of 0.5.
                    error += kDenominator / 2;
                }
            }

            input.Multiply(ref cachedPower);
            // The error introduced by a multiplication of a*b equals
            //   error_a + error_b + error_a*error_b/2^64 + 0.5
            // Substituting a with 'input' and b with 'cached_power' we have
            //   error_b = 0.5  (all cached powers have an error of less than 0.5 ulp),
            //   error_ab = 0 or 1 / kDenominator > error_a*error_b/ 2^64
            const int errorB     = kDenominator / 2;
            var       errorAb    = error == 0 ? 0 : 1; // We round up to 1.
            const int fixedError = kDenominator / 2;

            error += (ulong)(errorB + errorAb + fixedError);

            oldE = input.e;
            input.Normalize();
            error <<= oldE - input.e;

            // See if the double's significand changes if we add/subtract the error.
            var orderOfMagnitude         = DiyFp.kSignificandSize + input.e;
            var effectiveSignificandSize = IeeeDouble.SignificandSizeForOrderOfMagnitude(orderOfMagnitude);
            var precisionDigitsCount     = DiyFp.kSignificandSize - effectiveSignificandSize;

            if (precisionDigitsCount + kDenominatorLog >= DiyFp.kSignificandSize)
            {
                // This can only happen for very small denormals. In this case the
                // half-way multiplied by the denominator exceeds the range of an uint64.
                // Simply shift everything to the right.
                var shiftAmount = precisionDigitsCount + kDenominatorLog -
                                  DiyFp.kSignificandSize + 1;
                input.f >>= shiftAmount;
                input.e  += shiftAmount;
                // We add 1 for the lost precision of error, and kDenominator for
                // the lost precision of input.f().
                error = (error >> shiftAmount) + 1 + kDenominator;
                precisionDigitsCount -= shiftAmount;
            }

            // We use uint64_ts now. This only works if the DiyFp uses uint64_ts too.
            const ulong one64             = 1;
            var         precisionBitsMask = (one64 << precisionDigitsCount) - 1;
            var         precisionBits     = input.f & precisionBitsMask;
            var         halfWay           = one64 << (precisionDigitsCount - 1);

            precisionBits *= kDenominator;
            halfWay       *= kDenominator;
            var roundedInput = new DiyFp(input.f >> precisionDigitsCount, input.e + precisionDigitsCount);

            if (precisionBits >= halfWay + error)
            {
                roundedInput.f++;
            }
            // If the last_bits are too close to the half-way case than we are too
            // inaccurate and round down. In this case we return false so that we can
            // fall back to a more precise algorithm.

            result = new IeeeDouble(roundedInput).Value();
            // Too imprecise. The caller will have to fall back to a slower version.
            // However the returned number is guaranteed to be either the correct
            // double, or the next-lower double.
            return(halfWay - error >= precisionBits || precisionBits >= halfWay + error);
        }