Ejemplo n.º 1
0
        private static bool TryFormatInt64N(long value, byte precision, Span <byte> buffer, out int bytesWritten)
        {
            int digitCount      = FormattingHelpers.CountDigits(value);
            int groupSeparators = (int)FormattingHelpers.DivMod(digitCount, Utf8Constants.GroupSize, out long firstGroup);

            if (firstGroup == 0)
            {
                firstGroup = 3;
                groupSeparators--;
            }

            int trailingZeros = (precision == StandardFormat.NoPrecision) ? 2 : precision;
            int idx           = (int)((value >> 63) & 1) + digitCount + groupSeparators;

            bytesWritten = idx;
            if (trailingZeros > 0)
            {
                bytesWritten += trailingZeros + 1; // +1 for period.
            }
            if (buffer.Length < bytesWritten)
            {
                bytesWritten = 0;
                return(false);
            }

            ref byte utf8Bytes = ref MemoryMarshal.GetReference(buffer);
Ejemplo n.º 2
0
        private static bool TryFormatUInt64D(ulong value, byte precision, Span <byte> buffer, out int bytesWritten)
        {
            if (value <= long.MaxValue)
            {
                return(TryFormatInt64D((long)value, precision, buffer, out bytesWritten));
            }

            // Remove a single digit from the number. This will get it below long.MaxValue
            // Then we call the faster long version and follow-up with writing the last
            // digit. This ends up being faster by a factor of 2 than to just do the entire
            // operation using the unsigned versions.
            value = FormattingHelpers.DivMod(value, 10, out ulong lastDigit);

            if (precision != StandardFormat.NoPrecision && precision > 0)
            {
                precision -= 1;
            }

            if (!TryFormatInt64D((long)value, precision, buffer, out bytesWritten))
            {
                return(false);
            }

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

            ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference();
        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();
        /// <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.º 5
0
        /// <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.
                var 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;
                    }
                }

                totalSecondsRemaining = FormattingHelpers.DivMod((ulong)Math.Abs(value.Ticks), TimeSpan.TicksPerSecond, out ulong fraction64);
                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 = FormattingHelpers.DivMod(totalSecondsRemaining, 60 /* seconds per minute */, out seconds);
            }

            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 = FormattingHelpers.DivMod(totalMinutesRemaining, 60 /* minutes per hour */, out minutes);
            }

            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 = FormattingHelpers.DivMod((uint)totalHoursRemaining, 24 /* hours per day */, out hours);
            }

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