Esempio n. 1
0
        //
        // This method is copied directly from CoreRT (which is in turn a C#-ized version of the CoreCLR C++ code.)
        //
        public static void RoundNumber(ref NumberBuffer number, int pos)
        {
            number.CheckConsistency();

            Span <byte> digits = number.Digits;

            int i = 0;

            while (i < pos && digits[i] != 0)
            {
                i++;
            }

            if (i == pos && digits[i] >= (byte)'5')
            {
                while (i > 0 && digits[i - 1] == (byte)'9')
                {
                    i--;
                }

                if (i > 0)
                {
                    digits[i - 1]++;
                }
                else
                {
                    number.Scale++;
                    digits[0] = (byte)'1';
                    i         = 1;
                }
            }
            else
            {
                while (i > 0 && digits[i - 1] == (byte)'0')
                {
                    i--;
                }
            }
            if (i == 0)
            {
                number.Scale = 0;

                if (number.Kind == NumberBufferKind.Integer)
                {
                    number.IsNegative = false;
                }
            }
            digits[i] = 0;

            number.CheckConsistency();
        }
Esempio n. 2
0
        /// <summary>
        /// Formats a Decimal as a UTF8 string.
        /// </summary>
        /// <param name="value">Value to format</param>
        /// <param name="buffer">Buffer to write the UTF8-formatted value to</param>
        /// <param name="bytesWritten">Receives the length of the formatted text in bytes</param>
        /// <param name="format">The standard format to use</param>
        /// <returns>
        /// true for success. "bytesWritten" contains the length of the formatted text in bytes.
        /// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds.
        /// </returns>
        /// <remarks>
        /// Formats supported:
        ///     G/g  (default)
        ///     F/f             12.45       Fixed point
        ///     E/e             1.245000e1  Exponential
        /// </remarks>
        /// <exceptions>
        /// <cref>System.FormatException</cref> if the format is not valid for this data type.
        /// </exceptions>
        public static bool TryFormat(decimal value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default)
        {
            if (format.IsDefault)
            {
                format = 'G';
            }

            switch (format.Symbol)
            {
            case 'g':
            case 'G':
            {
                if (format.Precision != StandardFormat.NoPrecision)
                {
                    throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported);
                }
                NumberBuffer number = default;
                Number.DecimalToNumber(value, ref number);
                if (number.Digits[0] == 0)
                {
                    number.IsNegative = false;         // For Decimals, -0 must print as normal 0.
                }
                bool success = TryFormatDecimalG(ref number, buffer, out bytesWritten);
#if DEBUG
                // This DEBUG segment exists to close a code coverage hole inside TryFormatDecimalG(). Because we don't call RoundNumber() on this path, we have no way to feed
                // TryFormatDecimalG() a number where trailing zeros before the decimal point have been cropped. So if the chance comes up, we'll crop the zeroes
                // ourselves and make a second call to ensure we get the same outcome.
                if (success)
                {
                    Span <byte> digits    = number.Digits;
                    int         numDigits = number.NumDigits;
                    if (numDigits != 0 && number.Scale == numDigits && digits[numDigits - 1] == '0')
                    {
                        while (numDigits != 0 && digits[numDigits - 1] == '0')
                        {
                            digits[numDigits - 1] = 0;
                            numDigits--;
                        }

                        number.CheckConsistency();

                        byte[] buffer2  = new byte[buffer.Length];
                        bool   success2 = TryFormatDecimalG(ref number, buffer2, out int bytesWritten2);
                        Debug.Assert(success2);
                        Debug.Assert(bytesWritten2 == bytesWritten);
                        for (int i = 0; i < bytesWritten; i++)
                        {
                            Debug.Assert(buffer[i] == buffer2[i]);
                        }
                    }
                }
#endif // DEBUG
                return(success);
            }

            case 'f':
            case 'F':
            {
                NumberBuffer number = default;
                Number.DecimalToNumber(value, ref number);
                byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)2 : format.Precision;
                Number.RoundNumber(ref number, number.Scale + precision);
                Debug.Assert(!(number.Digits[0] == 0 && number.IsNegative));           // For Decimals, -0 must print as normal 0. As it happens, Number.RoundNumber already ensures this invariant.
                return(TryFormatDecimalF(ref number, buffer, out bytesWritten, precision));
            }

            case 'e':
            case 'E':
            {
                NumberBuffer number = default;
                Number.DecimalToNumber(value, ref number);
                byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)6 : format.Precision;
                Number.RoundNumber(ref number, precision + 1);
                Debug.Assert(!(number.Digits[0] == 0 && number.IsNegative));           // For Decimals, -0 must print as normal 0. As it happens, Number.RoundNumber already ensures this invariant.
                return(TryFormatDecimalE(ref number, buffer, out bytesWritten, precision, exponentSymbol: (byte)format.Symbol));
            }

            default:
                return(ThrowHelper.TryFormatThrowFormatException(out bytesWritten));
            }
        }
Esempio n. 3
0
        private static bool TryParseNumber(ReadOnlySpan <byte> text, ref NumberBuffer number, out int bytesConsumed, ParseNumberOptions options, out bool textUsedExponentNotation)
        {
            Debug.Assert(number.Digits[0] == 0 && number.Scale == 0 && !number.IsNegative, "Number not initialized to default(NumberBuffer)");

            textUsedExponentNotation = false;

            if (text.Length == 0)
            {
                bytesConsumed = 0;
                return(false);
            }

            Span <byte> digits = number.Digits;

            int srcIndex = 0;
            int dstIndex = 0;

            // Consume the leading sign if any.
            byte c = text[srcIndex];

            switch (c)
            {
            case Utf8Constants.Minus:
                number.IsNegative = true;
                goto case Utf8Constants.Plus;

            case Utf8Constants.Plus:
                srcIndex++;
                if (srcIndex == text.Length)
                {
                    bytesConsumed = 0;
                    return(false);
                }
                c = text[srcIndex];
                break;

            default:
                break;
            }

            int startIndexDigitsBeforeDecimal = srcIndex;

            // Throw away any leading zeroes
            while (srcIndex != text.Length)
            {
                c = text[srcIndex];
                if (c != '0')
                {
                    break;
                }
                srcIndex++;
            }

            if (srcIndex == text.Length)
            {
                digits[0]     = 0;
                number.Scale  = 0;
                bytesConsumed = srcIndex;
                number.CheckConsistency();
                return(true);
            }

            int startIndexNonLeadingDigitsBeforeDecimal = srcIndex;

            while (srcIndex != text.Length)
            {
                c = text[srcIndex];
                if ((c - 48u) > 9)
                {
                    break;
                }
                srcIndex++;
            }

            int numDigitsBeforeDecimal           = srcIndex - startIndexDigitsBeforeDecimal;
            int numNonLeadingDigitsBeforeDecimal = srcIndex - startIndexNonLeadingDigitsBeforeDecimal;

            Debug.Assert(dstIndex == 0);
            int numNonLeadingDigitsBeforeDecimalToCopy = Math.Min(numNonLeadingDigitsBeforeDecimal, NumberBuffer.BufferSize - 1);

            text.Slice(startIndexNonLeadingDigitsBeforeDecimal, numNonLeadingDigitsBeforeDecimalToCopy).CopyTo(digits);
            dstIndex     = numNonLeadingDigitsBeforeDecimalToCopy;
            number.Scale = numNonLeadingDigitsBeforeDecimal;

            if (srcIndex == text.Length)
            {
                bytesConsumed = srcIndex;
                number.CheckConsistency();
                return(true);
            }

            int numDigitsAfterDecimal = 0;

            if (c == Utf8Constants.Period)
            {
                //
                // Parse the digits after the decimal point.
                //

                srcIndex++;
                int startIndexDigitsAfterDecimal = srcIndex;
                while (srcIndex != text.Length)
                {
                    c = text[srcIndex];
                    if ((c - 48u) > 9)
                    {
                        break;
                    }
                    srcIndex++;
                }
                numDigitsAfterDecimal = srcIndex - startIndexDigitsAfterDecimal;

                int startIndexOfDigitsAfterDecimalToCopy = startIndexDigitsAfterDecimal;
                if (dstIndex == 0)
                {
                    // Not copied any digits to the Number struct yet. This means we must continue discarding leading zeroes even though they appeared after the decimal point.
                    while (startIndexOfDigitsAfterDecimalToCopy < srcIndex && text[startIndexOfDigitsAfterDecimalToCopy] == '0')
                    {
                        number.Scale--;
                        startIndexOfDigitsAfterDecimalToCopy++;
                    }
                }

                int numDigitsAfterDecimalToCopy = Math.Min(srcIndex - startIndexOfDigitsAfterDecimalToCopy, NumberBuffer.BufferSize - dstIndex - 1);
                text.Slice(startIndexOfDigitsAfterDecimalToCopy, numDigitsAfterDecimalToCopy).CopyTo(digits.Slice(dstIndex));
                dstIndex += numDigitsAfterDecimalToCopy;
                // We "should" really NUL terminate, but there are multiple places we'd have to do this and it is a precondition that the caller pass in a fully zero=initialized Number.

                if (srcIndex == text.Length)
                {
                    if (numDigitsBeforeDecimal == 0 && numDigitsAfterDecimal == 0)
                    {
                        // For compatibility. You can say "5." and ".5" but you can't say "."
                        bytesConsumed = 0;
                        return(false);
                    }

                    bytesConsumed = srcIndex;
                    number.CheckConsistency();
                    return(true);
                }
            }

            if (numDigitsBeforeDecimal == 0 && numDigitsAfterDecimal == 0)
            {
                bytesConsumed = 0;
                return(false);
            }

            if ((c & ~0x20u) != 'E')
            {
                bytesConsumed = srcIndex;
                number.CheckConsistency();
                return(true);
            }

            //
            // Parse the exponent after the "E"
            //
            textUsedExponentNotation = true;
            srcIndex++;

            if ((options & ParseNumberOptions.AllowExponent) == 0)
            {
                bytesConsumed = 0;
                return(false);
            }

            if (srcIndex == text.Length)
            {
                bytesConsumed = 0;
                return(false);
            }

            bool exponentIsNegative = false;

            c = text[srcIndex];
            switch (c)
            {
            case Utf8Constants.Minus:
                exponentIsNegative = true;
                goto case Utf8Constants.Plus;

            case Utf8Constants.Plus:
                srcIndex++;
                if (srcIndex == text.Length)
                {
                    bytesConsumed = 0;
                    return(false);
                }
                c = text[srcIndex];
                break;

            default:
                break;
            }

            if (!Utf8Parser.TryParseUInt32D(text.Slice(srcIndex), out uint absoluteExponent, out int bytesConsumedByExponent))
            {
                bytesConsumed = 0;
                return(false);
            }

            srcIndex += bytesConsumedByExponent;

            if (exponentIsNegative)
            {
                if (number.Scale < int.MinValue + (long)absoluteExponent)
                {
                    // A scale underflow means all non-zero digits are all so far to the right of the decimal point, no
                    // number format we have will be able to see them. Just pin the scale at the absolute minimum
                    // and let the converter produce a 0 with the max precision available for that type.
                    number.Scale = int.MinValue;
                }
                else
                {
                    number.Scale -= (int)absoluteExponent;
                }
            }
            else
            {
                if (number.Scale > int.MaxValue - (long)absoluteExponent)
                {
                    bytesConsumed = 0;
                    return(false);
                }
                number.Scale += (int)absoluteExponent;
            }

            bytesConsumed = srcIndex;
            number.CheckConsistency();
            return(true);
        }