Ejemplo n.º 1
0
 private static bool TryFormatInt64MultipleDigits(long value, Span <byte> destination, out int bytesWritten)
 {
     if (value < 0)
     {
         value = -value;
         int digitCount = FormattingHelpers.CountDigits((ulong)value);
         // WriteDigits does not do bounds checks
         if (digitCount >= destination.Length)
         {
             bytesWritten = 0;
             return(false);
         }
         destination[0] = Utf8Constants.Minus;
         bytesWritten   = digitCount + 1;
         FormattingHelpers.WriteDigits((ulong)value, destination.Slice(1, digitCount));
         return(true);
     }
     else
     {
         return(TryFormatUInt64MultipleDigits((ulong)value, destination, out bytesWritten));
     }
 }
        // Split long into two parts that can each fit in a uint - {1-10 digits}{9 digits}
        private static bool TryFormatInt64MoreThanNegativeBillionMaxUInt(long value, Span <byte> destination, out int bytesWritten)
        {
            uint overNineDigits = (uint)(value / Utf8Constants.Billion);
            uint lastNineDigits = (uint)(value - (overNineDigits * Utf8Constants.Billion));

            int digitCountOverNineDigits = FormattingHelpers.CountDigits(overNineDigits);

            Debug.Assert(digitCountOverNineDigits >= 1 && digitCountOverNineDigits <= 10);
            int digitCount = digitCountOverNineDigits + 9;

            // WriteDigits does not do bounds checks
            if (digitCount >= destination.Length)
            {
                bytesWritten = 0;
                return(false);
            }
            destination[0] = Utf8Constants.Minus;
            bytesWritten   = digitCount + 1;
            FormattingHelpers.WriteDigits(overNineDigits, destination.Slice(1, digitCountOverNineDigits));
            FormattingHelpers.WriteDigits(lastNineDigits, destination.Slice(digitCountOverNineDigits + 1, 9));
            return(true);
        }
        private static bool TryFormatUInt64N(ulong value, byte precision, Span <byte> buffer, out int bytesWritten)
        {
            if (value <= long.MaxValue)
            {
                return(TryFormatInt64N((long)value, precision, buffer, out bytesWritten));
            }

            // The ulong path is much slower than the long path here, so we are doing the last group
            // inside this method plus the zero padding but routing to the long version for the rest.
            value = FormattingHelpers.DivMod(value, 1000, out ulong lastGroup);

            if (!TryFormatInt64N((long)value, 0, buffer, out bytesWritten))
            {
                return(false);
            }

            if (precision == StandardFormat.NoPrecision)
            {
                precision = 2;
            }

            int idx = bytesWritten;

            // Since this method routes entirely to the long version if the number is smaller than
            // long.MaxValue, we are guaranteed to need to write 3 more digits here before the set
            // of trailing zeros.

            bytesWritten += 4; // 3 digits + group separator
            if (precision > 0)
            {
                bytesWritten += precision + 1; // +1 for period.
            }
            if (buffer.Length < bytesWritten)
            {
                bytesWritten = 0;
                return(false);
            }

            ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference();
        private static bool TryFormatUInt64(ulong value, Span <byte> destination, out int bytesWritten, StandardFormat format)
        {
            if (format.IsDefault)
            {
                return(TryFormatUInt64Default(value, destination, out bytesWritten));
            }

            switch (format.Symbol)
            {
            case 'G':
            case 'g':
            case 'R':
            case 'r':     // treat 'R'/'r' (which historically was only for floating-point) as being equivalent to 'g', rather than throwing
                if (format.HasPrecision)
                {
                    throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported);     // With a precision, 'G' can produce exponential format, even for integers.
                }
                return(TryFormatUInt64D(value, format.Precision, destination, insertNegationSign: false, out bytesWritten));

            case 'd':
            case 'D':
                return(TryFormatUInt64D(value, format.Precision, destination, insertNegationSign: false, out bytesWritten));

            case 'n':
            case 'N':
                return(TryFormatUInt64N(value, format.Precision, destination, insertNegationSign: false, out bytesWritten));

            case 'x':
                return(TryFormatUInt64X(value, format.Precision, true /* useLower */, destination, out bytesWritten));

            case 'X':
                return(TryFormatUInt64X(value, format.Precision, false /* useLower */, destination, out bytesWritten));

            default:
                return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten));
            }
        }
Ejemplo n.º 5
0
        private static bool TryFormatUInt64MoreThanBillionMaxUInt(ulong value, Span <byte> destination, out int bytesWritten)
        {
            ulong overNineDigits     = value / Utf8Constants.Billion;
            uint  lastNineDigits     = (uint)(value - (overNineDigits * Utf8Constants.Billion));
            uint  overEighteenDigits = (uint)(overNineDigits / Utf8Constants.Billion);
            uint  middleNineDigits   = (uint)(overNineDigits - (overEighteenDigits * Utf8Constants.Billion));

            int digitCountOverEighteenDigits = FormattingHelpers.CountDigits(overEighteenDigits);

            Debug.Assert(digitCountOverEighteenDigits >= 1 && digitCountOverEighteenDigits <= 2);
            int digitCount = digitCountOverEighteenDigits + 18;

            // WriteDigits does not do bounds checks
            if (digitCount > destination.Length)
            {
                bytesWritten = 0;
                return(false);
            }
            bytesWritten = digitCount;
            FormattingHelpers.WriteDigits(overEighteenDigits, destination.Slice(0, digitCountOverEighteenDigits));
            FormattingHelpers.WriteDigits(middleNineDigits, destination.Slice(digitCountOverEighteenDigits, 9));
            FormattingHelpers.WriteDigits(lastNineDigits, destination.Slice(digitCountOverEighteenDigits + 9, 9));
            return(true);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Formats a Guid as a UTF8 string.
        /// </summary>
        /// <param name="value">Value to format</param>
        /// <param name="destination">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:
        ///     D (default)     nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn
        ///     B               {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
        ///     P               (nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn)
        ///     N               nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        /// </remarks>
        /// <exceptions>
        /// <cref>System.FormatException</cref> if the format is not valid for this data type.
        /// </exceptions>
        public static bool TryFormat(Guid value, Span <byte> destination, out int bytesWritten, StandardFormat format = default)
        {
            const int INSERT_DASHES       = unchecked ((int)0x80000000);
            const int NO_DASHES           = 0;
            const int INSERT_CURLY_BRACES = (CloseBrace << 16) | (OpenBrace << 8);
            const int INSERT_ROUND_BRACES = (CloseParen << 16) | (OpenParen << 8);
            const int NO_BRACES           = 0;
            const int LEN_GUID_BASE       = 32;
            const int LEN_ADD_DASHES      = 4;
            const int LEN_ADD_BRACES      = 2;

            // This is a 32-bit value whose contents (where 0 is the low byte) are:
            // 0th byte: minimum required length of the output buffer,
            // 1st byte: the ASCII byte to insert for the opening brace position (or 0 if no braces),
            // 2nd byte: the ASCII byte to insert for the closing brace position (or 0 if no braces),
            // 3rd byte: high bit set if dashes are to be inserted.
            //
            // The reason for keeping a single flag instead of separate vars is that we can avoid register spillage
            // as we build up the output value.
            int flags;

            switch (FormattingHelpers.GetSymbolOrDefault(format, 'D'))
            {
            case 'D':     // nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn
                flags = INSERT_DASHES + NO_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES;
                break;

            case 'B':     // {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
                flags = INSERT_DASHES + INSERT_CURLY_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES + LEN_ADD_BRACES;
                break;

            case 'P':     // (nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn)
                flags = INSERT_DASHES + INSERT_ROUND_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES + LEN_ADD_BRACES;
                break;

            case 'N':     // nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
                flags = NO_BRACES + NO_DASHES + LEN_GUID_BASE;
                break;

            default:
                return(ThrowHelper.TryFormatThrowFormatException(out bytesWritten));
            }

            // At this point, the low byte of flags contains the minimum required length

            if ((byte)flags > destination.Length)
            {
                bytesWritten = 0;
                return(false);
            }

            bytesWritten = (byte)flags;
            flags      >>= 8;

            // At this point, the low byte of flags contains the opening brace char (if any)

            if ((byte)flags != 0)
            {
                destination[0] = (byte)flags;
                destination    = destination.Slice(1);
            }
            flags >>= 8;

            // At this point, the low byte of flags contains the closing brace char (if any)
            // And since we're performing arithmetic shifting the high bit of flags is set (flags is negative) if dashes are required

            DecomposedGuid guidAsBytes = default;

            guidAsBytes.Guid = value;

            // When a GUID is blitted, the first three components are little-endian, and the last component is big-endian.

            // The line below forces the JIT to hoist the bounds check for the following segment.
            // The JIT will optimize away the read, but it cannot optimize away the bounds check
            // because it may have an observable side effect (throwing).
            // We use 8 instead of 7 so that we also capture the dash if we're asked to insert one.

            { var unused = destination[8]; }
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte03, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte02, destination, 2, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte01, destination, 4, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte00, destination, 6, FormattingHelpers.HexCasing.Lowercase);

            if (flags < 0 /* use dash? */)
            {
                destination[8] = Dash;
                destination    = destination.Slice(9);
            }
            else
            {
                destination = destination.Slice(8);
            }

            { var unused = destination[4]; }
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte05, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte04, destination, 2, FormattingHelpers.HexCasing.Lowercase);

            if (flags < 0 /* use dash? */)
            {
                destination[4] = Dash;
                destination    = destination.Slice(5);
            }
            else
            {
                destination = destination.Slice(4);
            }

            { var unused = destination[4]; }
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte07, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte06, destination, 2, FormattingHelpers.HexCasing.Lowercase);

            if (flags < 0 /* use dash? */)
            {
                destination[4] = Dash;
                destination    = destination.Slice(5);
            }
            else
            {
                destination = destination.Slice(4);
            }

            { var unused = destination[4]; }
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte08, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte09, destination, 2, FormattingHelpers.HexCasing.Lowercase);

            if (flags < 0 /* use dash? */)
            {
                destination[4] = Dash;
                destination    = destination.Slice(5);
            }
            else
            {
                destination = destination.Slice(4);
            }

            { var unused = destination[11]; } // can't hoist bounds check on the final brace (if exists)
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte10, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte11, destination, 2, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte12, destination, 4, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte13, destination, 6, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte14, destination, 8, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte15, destination, 10, FormattingHelpers.HexCasing.Lowercase);

            if ((byte)flags != 0)
            {
                destination[12] = (byte)flags;
            }

            return(true);
        }
        /// <summary>
        /// Formats a TimeSpan as a UTF8 string.
        /// </summary>
        /// <param name="value">Value to format</param>
        /// <param name="destination">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:
        ///     c/t/T (default) [-][d.]hh:mm:ss[.fffffff]              (constant format)
        ///     G               [-]d:hh:mm:ss.fffffff                  (general long)
        ///     g               [-][d:][h]h:mm:ss[.f[f[f[f[f[f[f]]]]]] (general short)
        /// </remarks>
        /// <exceptions>
        /// <cref>System.FormatException</cref> if the format is not valid for this data type.
        /// </exceptions>
        public static bool TryFormat(TimeSpan value, Span <byte> destination, out int bytesWritten, StandardFormat format = default)
        {
            char symbol = FormattingHelpers.GetSymbolOrDefault(format, 'c');

            switch (symbol)
            {
            case 'c':
            case 'G':
            case 'g':
                break;

            case 't':
            case 'T':
                symbol = 'c';
                break;

            default:
                return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten));
            }

            // First, calculate how large an output buffer is needed to hold the entire output.

            int requiredOutputLength = 8; // start with "hh:mm:ss" and adjust as necessary

            uint  fraction;
            ulong totalSecondsRemaining;

            {
                // Turn this into a non-negative TimeSpan if possible.
                long ticks = value.Ticks;
                if (ticks < 0)
                {
                    ticks = -ticks;
                    if (ticks < 0)
                    {
                        Debug.Assert(ticks == long.MinValue /* -9223372036854775808 */);

                        // We computed these ahead of time; they're straight from the decimal representation of Int64.MinValue.
                        fraction = 4775808;
                        totalSecondsRemaining = 922337203685;
                        goto AfterComputeFraction;
                    }
                }

                ulong fraction64;
                (totalSecondsRemaining, fraction64) = Math.DivRem((ulong)Math.Abs(value.Ticks), TimeSpan.TicksPerSecond);
                fraction = (uint)fraction64;
            }

AfterComputeFraction:

            int fractionDigits = 0;

            if (symbol == 'c')
            {
                // Only write out the fraction if it's non-zero, and in that
                // case write out the entire fraction (all digits).
                if (fraction != 0)
                {
                    fractionDigits = Utf8Constants.DateTimeNumFractionDigits;
                }
            }
            else if (symbol == 'G')
            {
                // Always write out the fraction, even if it's zero.
                fractionDigits = Utf8Constants.DateTimeNumFractionDigits;
            }
            else
            {
                // Only write out the fraction if it's non-zero, and in that
                // case write out only the most significant digits.
                if (fraction != 0)
                {
                    fractionDigits = Utf8Constants.DateTimeNumFractionDigits - FormattingHelpers.CountDecimalTrailingZeros(fraction, out fraction);
                }
            }

            Debug.Assert(fraction < 10_000_000);

            // If we're going to write out a fraction, also need to write the leading decimal.
            if (fractionDigits != 0)
            {
                requiredOutputLength += fractionDigits + 1;
            }

            ulong totalMinutesRemaining = 0;
            ulong seconds = 0;

            if (totalSecondsRemaining > 0)
            {
                // Only compute minutes if the TimeSpan has an absolute value of >= 1 minute.
                (totalMinutesRemaining, seconds) = Math.DivRem(totalSecondsRemaining, 60 /* seconds per minute */);
            }

            Debug.Assert(seconds < 60);

            ulong totalHoursRemaining = 0;
            ulong minutes             = 0;

            if (totalMinutesRemaining > 0)
            {
                // Only compute hours if the TimeSpan has an absolute value of >= 1 hour.
                (totalHoursRemaining, minutes) = Math.DivRem(totalMinutesRemaining, 60 /* minutes per hour */);
            }

            Debug.Assert(minutes < 60);

            // At this point, we can switch over to 32-bit divmod since the data has shrunk far enough.
            Debug.Assert(totalHoursRemaining <= uint.MaxValue);

            uint days  = 0;
            uint hours = 0;

            if (totalHoursRemaining > 0)
            {
                // Only compute days if the TimeSpan has an absolute value of >= 1 day.
                (days, hours) = Math.DivRem((uint)totalHoursRemaining, 24 /* hours per day */);
            }

            Debug.Assert(hours < 24);

            int hourDigits = 2;

            if (hours < 10 && symbol == 'g')
            {
                // Only writing a one-digit hour, not a two-digit hour
                hourDigits--;
                requiredOutputLength--;
            }

            int dayDigits = 0;

            if (days == 0)
            {
                if (symbol == 'G')
                {
                    requiredOutputLength += 2; // for the leading "0:"
                    dayDigits             = 1;
                }
            }
            else
            {
                dayDigits             = FormattingHelpers.CountDigits(days);
                requiredOutputLength += dayDigits + 1; // for the leading "d:" (or "d.")
            }

            if (value.Ticks < 0)
            {
                requiredOutputLength++; // for the leading '-' sign
            }

            if (destination.Length < requiredOutputLength)
            {
                bytesWritten = 0;
                return(false);
            }

            bytesWritten = requiredOutputLength;

            int idx = 0;

            // Write leading '-' if necessary
            if (value.Ticks < 0)
            {
                destination[idx++] = Utf8Constants.Minus;
            }

            // Write day (and separator) if necessary
            if (dayDigits > 0)
            {
                FormattingHelpers.WriteDigits(days, destination.Slice(idx, dayDigits));
                idx += dayDigits;
                destination[idx++] = (symbol == 'c') ? Utf8Constants.Period : Utf8Constants.Colon;
            }

            // Write "[h]h:mm:ss"
            FormattingHelpers.WriteDigits(hours, destination.Slice(idx, hourDigits));
            idx += hourDigits;
            destination[idx++] = Utf8Constants.Colon;
            FormattingHelpers.WriteDigits((uint)minutes, destination.Slice(idx, 2));
            idx += 2;
            destination[idx++] = Utf8Constants.Colon;
            FormattingHelpers.WriteDigits((uint)seconds, destination.Slice(idx, 2));
            idx += 2;

            // Write fraction (and separator) if necessary
            if (fractionDigits > 0)
            {
                destination[idx++] = Utf8Constants.Period;
                FormattingHelpers.WriteDigits(fraction, destination.Slice(idx, fractionDigits));
                idx += fractionDigits;
            }

            // And we're done!

            Debug.Assert(idx == requiredOutputLength);
            return(true);
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Formats a Boolean as a UTF8 string.
        /// </summary>
        /// <param name="value">Value to format</param>
        /// <param name="destination">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 (default)   True/False
        ///     l             true/false
        /// </remarks>
        /// <exceptions>
        /// <cref>System.FormatException</cref> if the format is not valid for this data type.
        /// </exceptions>
        public static bool TryFormat(bool value, Span <byte> destination, out int bytesWritten, StandardFormat format = default)
        {
            char symbol = FormattingHelpers.GetSymbolOrDefault(format, 'G');

            if (value)
            {
                if (symbol == 'G')
                {
                    // By having each branch perform its own call to TryWriteUInt32BigEndian, we ensure that a
                    // constant value is passed to this routine, which means the compiler can reverse endianness
                    // at compile time instead of runtime if necessary.
                    const uint TrueValueUppercase = ('T' << 24) + ('r' << 16) + ('u' << 8) + ('e' << 0);
                    if (!BinaryPrimitives.TryWriteUInt32BigEndian(destination, TrueValueUppercase))
                    {
                        goto BufferTooSmall;
                    }
                }
                else if (symbol == 'l')
                {
                    const uint TrueValueLowercase = ('t' << 24) + ('r' << 16) + ('u' << 8) + ('e' << 0);
                    if (!BinaryPrimitives.TryWriteUInt32BigEndian(destination, TrueValueLowercase))
                    {
                        goto BufferTooSmall;
                    }
                }
                else
                {
                    goto BadFormat;
                }

                bytesWritten = 4;
                return(true);
            }
            else
            {
                if (symbol == 'G')
                {
                    // This check can't be performed earlier because we need to throw if an invalid symbol is
                    // provided, even if the buffer is too small.
                    if (destination.Length <= 4)
                    {
                        goto BufferTooSmall;
                    }

                    const uint FalsValueUppercase = ('F' << 24) + ('a' << 16) + ('l' << 8) + ('s' << 0);
                    BinaryPrimitives.WriteUInt32BigEndian(destination, FalsValueUppercase);
                }
                else if (symbol == 'l')
                {
                    if (destination.Length <= 4)
                    {
                        goto BufferTooSmall;
                    }

                    const uint FalsValueLowercase = ('f' << 24) + ('a' << 16) + ('l' << 8) + ('s' << 0);
                    BinaryPrimitives.WriteUInt32BigEndian(destination, FalsValueLowercase);
                }
                else
                {
                    goto BadFormat;
                }

                destination[4] = (byte)'e';
                bytesWritten   = 5;
                return(true);
            }

BufferTooSmall:
            bytesWritten = 0;
            return(false);

BadFormat:
            return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten));
        }
        /// <summary>
        /// Formats a TimeSpan 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:
        ///     c/t/T (default) [-][d.]hh:mm:ss[.fffffff]             (constant format)
        ///     G               [-]d:hh:mm:ss.fffffff                 (general long)
        ///     g               [-][d:]h:mm:ss[.f[f[f[f[f[f[f[]]]]]]] (general short)
        /// </remarks>
        /// <exceptions>
        /// <cref>System.FormatException</cref> if the format is not valid for this data type.
        /// </exceptions>
        public static bool TryFormat(TimeSpan value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default)
        {
            char symbol = format.IsDefault ? 'c' : format.Symbol;

            switch (symbol)
            {
            case 'G':
            case 'g':
            case 'c':
            case 't':
            case 'T':
            {
                bool longForm = (symbol == 'G');
                bool constant = (symbol == 't' || symbol == 'T' || symbol == 'c');

                long ticks = value.Ticks;
                int  days  = (int)FormattingHelpers.DivMod(ticks, TimeSpan.TicksPerDay, out long timeLeft);

                bool showSign = false;
                if (ticks < 0)
                {
                    showSign = true;
                    days     = -days;
                    timeLeft = -timeLeft;
                }

                int hours   = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerHour, out timeLeft);
                int minutes = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerMinute, out timeLeft);
                int seconds = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerSecond, out long fraction);

                int dayDigits      = 0;
                int hourDigits     = (constant || longForm || hours > 9) ? 2 : 1;
                int fractionDigits = 0;

                bytesWritten = hourDigits + 6;         // [h]h:mm:ss
                if (showSign)
                {
                    bytesWritten += 1;          // [-]
                }
                if (longForm || days > 0)
                {
                    dayDigits     = FormattingHelpers.CountDigits(days);
                    bytesWritten += dayDigits + 1;         // [d'.']
                }
                if (longForm || fraction > 0)
                {
                    fractionDigits = (longForm || constant) ? Utf8Constants.DateTimeNumFractionDigits : FormattingHelpers.CountFractionDigits(fraction);
                    bytesWritten  += fractionDigits + 1;        // ['.'fffffff] or ['.'FFFFFFF] for short-form
                }

                if (buffer.Length < bytesWritten)
                {
                    bytesWritten = 0;
                    return(false);
                }

                ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference();
                int      idx       = 0;

                if (showSign)
                {
                    Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Minus;
                }

                if (dayDigits > 0)
                {
                    idx += FormattingHelpers.WriteDigits(days, dayDigits, ref utf8Bytes, idx);
                    Unsafe.Add(ref utf8Bytes, idx++) = constant ? Utf8Constants.Period : Utf8Constants.Colon;
                }

                idx += FormattingHelpers.WriteDigits(hours, hourDigits, ref utf8Bytes, idx);
                Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Colon;

                idx += FormattingHelpers.WriteDigits(minutes, 2, ref utf8Bytes, idx);
                Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Colon;

                idx += FormattingHelpers.WriteDigits(seconds, 2, ref utf8Bytes, idx);

                if (fractionDigits > 0)
                {
                    Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Period;
                    idx += FormattingHelpers.WriteFractionDigits(fraction, fractionDigits, ref utf8Bytes, idx);
                }

                return(true);
            }
Ejemplo n.º 10
0
        /// <summary>
        /// Formats a Decimal as a UTF8 string.
        /// </summary>
        /// <param name="value">Value to format</param>
        /// <param name="destination">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 unsafe bool TryFormat(decimal value, Span <byte> destination, 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);
                }

                Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, stackalloc byte[Number.DecimalNumberBufferLength]);

                Number.DecimalToNumber(ref value, ref number);
                if (number.Digits[0] == 0)
                {
                    number.IsNegative = false;         // For Decimals, -0 must print as normal 0.
                }
                bool success = TryFormatDecimalG(ref number, destination, 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.DigitsCount;
                    if (numDigits != 0 && number.Scale == numDigits && digits[numDigits - 1] == '0')
                    {
                        while (numDigits != 0 && digits[numDigits - 1] == '0')
                        {
                            digits[numDigits - 1] = 0;
                            numDigits--;
                        }

                        number.DigitsCount = numDigits;
                        number.CheckConsistency();

                        byte[] buffer2  = new byte[destination.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(destination[i] == buffer2[i]);
                        }
                    }
                }
#endif // DEBUG
                return(success);
            }

            case 'f':
            case 'F':
            {
                Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, stackalloc byte[Number.DecimalNumberBufferLength]);

                Number.DecimalToNumber(ref value, ref number);
                byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)2 : format.Precision;
                Number.RoundNumber(ref number, number.Scale + precision, isCorrectlyRounded: false);
                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, destination, out bytesWritten, precision));
            }

            case 'e':
            case 'E':
            {
                Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, stackalloc byte[Number.DecimalNumberBufferLength]);

                Number.DecimalToNumber(ref value, ref number);
                byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)6 : format.Precision;
                Number.RoundNumber(ref number, precision + 1, isCorrectlyRounded: false);
                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, destination, out bytesWritten, precision, exponentSymbol: (byte)format.Symbol));
            }

            default:
                return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten));
            }
        }
Ejemplo n.º 11
0
        private static bool TryFormatFloatingPoint <T>(T value, Span <byte> destination, out int bytesWritten, StandardFormat format) where T : IFormattable, ISpanFormattable
        {
            if (format.IsDefault)
            {
                format = 'G';
            }

            switch (format.Symbol)
            {
            case 'g':
            case 'G':
                if (format.Precision != StandardFormat.NoPrecision)
                {
                    throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported);
                }
                break;

            case 'f':
            case 'F':
            case 'e':
            case 'E':
                break;

            default:
                return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten));
            }

            Span <char> formatText      = stackalloc char[StandardFormat.FormatStringLength];
            int         formattedLength = format.Format(formatText);

            formatText = formatText.Slice(0, formattedLength);

            // We first try to format into a stack-allocated buffer, and if it succeeds, we can avoid
            // all allocation.  If that fails, we fall back to allocating strings.  If it proves impactful,
            // that allocation (as well as roundtripping from byte to char and back to byte) could be avoided by
            // calling into a refactored Number.FormatSingle/Double directly.

            const int           StackBufferLength = 128; // large enough to handle the majority cases
            Span <char>         stackBuffer       = stackalloc char[StackBufferLength];
            ReadOnlySpan <char> utf16Text         = stackalloc char[0];

            // Try to format into the stack buffer.  If we're successful, we can avoid all allocations.
            if (value.TryFormat(stackBuffer, out formattedLength, formatText, CultureInfo.InvariantCulture))
            {
                utf16Text = stackBuffer.Slice(0, formattedLength);
            }
            else
            {
                // The stack buffer wasn't large enough.  If the destination buffer isn't at least as
                // big as the stack buffer, we know the whole operation will eventually fail, so we
                // can just fail now.
                if (destination.Length <= StackBufferLength)
                {
                    bytesWritten = 0;
                    return(false);
                }

                // Fall back to using a string format and allocating a string for the resulting formatted value.
                utf16Text = value.ToString(new string(formatText), CultureInfo.InvariantCulture);
            }

            // Copy the value to the destination, if it's large enough.

            if (utf16Text.Length > destination.Length)
            {
                bytesWritten = 0;
                return(false);
            }

            for (int i = 0; i < utf16Text.Length; i++)
            {
                Debug.Assert(utf16Text[i] < 128, "A culture-invariant ToString() of a floating point expected to produce ASCII characters only.");
                destination[i] = (byte)utf16Text[i];
            }

            bytesWritten = utf16Text.Length;
            return(true);
        }