public static bool TryFormat(bool value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default) { if (value) { if (buffer.Length < 8) // 4 chars for "true" = 8 bytes { bytesWritten = 0; return(false); } if (format.IsDefault || format.Symbol == 'G') { s_True.CopyTo(buffer); bytesWritten = s_True.Length; return(true); } else if (format.Symbol == 'l') { s_true.CopyTo(buffer); bytesWritten = s_true.Length; return(true); } else { throw new FormatException(); } } else { if (buffer.Length < 10) // 5 chars for "false" = 10 bytes { bytesWritten = 0; return(false); } if (format.IsDefault || format.Symbol == 'G') { s_False.CopyTo(buffer); bytesWritten = s_False.Length; return(true); } else if (format.Symbol == 'l') { s_false.CopyTo(buffer); bytesWritten = s_false.Length; return(true); } else { throw new FormatException(); } } }
public static void Append <TFormatter, T>(this TFormatter formatter, T value, StandardFormat format = default) where T : IBufferFormattable where TFormatter : ITextOutput { while (!formatter.TryAppend(value, format)) { formatter.Enlarge(); } }
public static void Append <TFormatter>(this TFormatter formatter, DateTime value, StandardFormat format = default) where TFormatter : ITextOutput { while (!formatter.TryAppend(value, format)) { formatter.Enlarge(); } }
public static void StandardFormatParseNegative(string badFormatString) { Assert.Throws <FormatException>(() => StandardFormat.Parse(badFormatString)); }
public static bool TryFormat <T>(T value, Span <byte> destination, out int bytesWritten, StandardFormat format = default) { return(FormatFunctionality <T> .Invoke(value, destination, out bytesWritten, format)); }
public static void Append <TFormatter>(this TFormatter formatter, double value, SymbolTable symbolTable, StandardFormat format = default) where TFormatter : IOutput { while (!formatter.TryAppend(value, symbolTable, format)) { formatter.Enlarge(); } }
private static bool TryFormatStandard(TimeSpan value, StandardFormat format, string decimalSeparator, Span <char> destination, out int charsWritten) { Debug.Assert(format == StandardFormat.C || format == StandardFormat.G || format == StandardFormat.g); // 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) { requiredOutputLength = 9; // requiredOutputLength + 1 for the leading '-' sign 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 = Math.DivRem((ulong)ticks, TimeSpan.TicksPerSecond, out ulong fraction64); fraction = (uint)fraction64; } AfterComputeFraction: // Only write out the fraction if it's non-zero, and in that // case write out the entire fraction (all digits). Debug.Assert(fraction < 10_000_000); int fractionDigits = 0; switch (format) { case StandardFormat.C: // "c": Write out a fraction only if it's non-zero, and write out all 7 digits of it. if (fraction != 0) { fractionDigits = DateTimeFormat.MaxSecondsFractionDigits; requiredOutputLength += fractionDigits + 1; // digits plus leading decimal separator } break; case StandardFormat.G: // "G": Write out a fraction regardless of whether it's 0, and write out all 7 digits of it. fractionDigits = DateTimeFormat.MaxSecondsFractionDigits; requiredOutputLength += fractionDigits + 1; // digits plus leading decimal separator break; default: // "g": Write out a fraction only if it's non-zero, and write out only the most significant digits. Debug.Assert(format == StandardFormat.g); if (fraction != 0) { fractionDigits = DateTimeFormat.MaxSecondsFractionDigits - FormattingHelpers.CountDecimalTrailingZeros(fraction, out fraction); requiredOutputLength += fractionDigits + 1; // digits plus leading decimal separator } break; } ulong totalMinutesRemaining = 0, seconds = 0; if (totalSecondsRemaining > 0) { // Only compute minutes if the TimeSpan has an absolute value of >= 1 minute. totalMinutesRemaining = Math.DivRem(totalSecondsRemaining, 60 /* seconds per minute */, out seconds); Debug.Assert(seconds < 60); } ulong totalHoursRemaining = 0, minutes = 0; if (totalMinutesRemaining > 0) { // Only compute hours if the TimeSpan has an absolute value of >= 1 hour. totalHoursRemaining = Math.DivRem(totalMinutesRemaining, 60 /* minutes per hour */, out minutes); Debug.Assert(minutes < 60); } // At this point, we can switch over to 32-bit DivRem since the data has shrunk far enough. Debug.Assert(totalHoursRemaining <= uint.MaxValue); uint days = 0, hours = 0; if (totalHoursRemaining > 0) { // Only compute days if the TimeSpan has an absolute value of >= 1 day. days = Math.DivRem((uint)totalHoursRemaining, 24 /* hours per day */, out hours); Debug.Assert(hours < 24); } int hourDigits = 2; if (format == StandardFormat.g && hours < 10) { // "g": Only writing a one-digit hour, rather than expected two-digit hour hourDigits = 1; requiredOutputLength--; } int dayDigits = 0; if (days > 0) { dayDigits = FormattingHelpers.CountDigits(days); Debug.Assert(dayDigits <= 8); requiredOutputLength += dayDigits + 1; // for the leading "d." } else if (format == StandardFormat.G) { // "G": has a leading "0:" if days is 0 requiredOutputLength += 2; dayDigits = 1; } if (destination.Length < requiredOutputLength) { charsWritten = 0; return(false); } // Write leading '-' if necessary int idx = 0; if (value.Ticks < 0) { destination[idx++] = '-'; } // Write day and separator, if necessary if (dayDigits != 0) { WriteDigits(days, destination.Slice(idx, dayDigits)); idx += dayDigits; destination[idx++] = format == StandardFormat.C ? '.' : ':'; } // Write "[h]h:mm:ss Debug.Assert(hourDigits == 1 || hourDigits == 2); if (hourDigits == 2) { WriteTwoDigits(hours, destination.Slice(idx)); idx += 2; } else { destination[idx++] = (char)('0' + hours); } destination[idx++] = ':'; WriteTwoDigits((uint)minutes, destination.Slice(idx)); idx += 2; destination[idx++] = ':'; WriteTwoDigits((uint)seconds, destination.Slice(idx)); idx += 2; // Write fraction and separator, if necessary if (fractionDigits != 0) { if (format == StandardFormat.C) { destination[idx++] = '.'; } else if (decimalSeparator.Length == 1) { destination[idx++] = decimalSeparator[0]; } else { decimalSeparator.AsSpan().CopyTo(destination); idx += decimalSeparator.Length; } WriteDigits(fraction, destination.Slice(idx, fractionDigits)); idx += fractionDigits; } Debug.Assert(idx == requiredOutputLength); charsWritten = requiredOutputLength; return(true); }
public static bool TryFormat(sbyte value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default, SymbolTable symbolTable = null) { if (symbolTable == null || symbolTable == SymbolTable.InvariantUtf8) { return(Utf8Formatter.TryFormat(value, buffer, out bytesWritten, format)); } else if (symbolTable == SymbolTable.InvariantUtf16) { return(Utf16Formatter.TryFormat(value, buffer, out bytesWritten, format)); } else { return(TryFormatInt64(value, 0xff, buffer, out bytesWritten, format, symbolTable)); } }
public static void Append <TFormatter>(this TFormatter formatter, float value, SymbolTable symbolTable, StandardFormat format = default) where TFormatter : IOutput { while (!formatter.TryAppend(value, symbolTable, format)) { formatter.GetMoreMemory(); } }
public static bool TryFormat(float value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default, SymbolTable symbolTable = null) { if (format.IsDefault) { format = 'G'; } symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; switch (format.Symbol) { case 'G': return(CustomFormatter.TryFormatNumber(value, true, buffer, out bytesWritten, format, symbolTable)); default: throw new NotSupportedException(); } }
public static bool TryFormat(DateTimeOffset value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default, SymbolTable symbolTable = null) { if (symbolTable == null || symbolTable == SymbolTable.InvariantUtf8) { return(Utf8Formatter.TryFormat(value, buffer, out bytesWritten, format)); } else if (symbolTable == SymbolTable.InvariantUtf16) { return(Utf16Formatter.TryFormat(value, buffer, out bytesWritten, format)); } else { throw new NotSupportedException(); } }
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': return(TryFormatTimeSpan(value, symbol, buffer, out bytesWritten)); default: throw new NotSupportedException(); } }
public static bool TryFormat(DateTime value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default) { char symbol = format.IsDefault ? 'G' : format.Symbol; switch (symbol) { case 'R': return(TryFormatRfc1123(value, buffer, out bytesWritten)); case 'l': return(TryFormatRfc1123Lowercase(value, buffer, out bytesWritten)); case 'O': return(TryFormatO(value, NullOffset, buffer, out bytesWritten)); case 'G': return(TryFormatG(value, NullOffset, buffer, out bytesWritten)); default: throw new NotSupportedException(); } }
public static bool TryFormat(DateTimeOffset value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default) { TimeSpan offset = NullOffset; char symbol = format.Symbol; if (format.IsDefault) { symbol = 'G'; offset = value.Offset; } switch (symbol) { case 'R': return(TryFormatRfc1123(value.UtcDateTime, buffer, out bytesWritten)); case 'l': return(TryFormatRfc1123Lowercase(value.UtcDateTime, buffer, out bytesWritten)); case 'O': return(TryFormatO(value.DateTime, value.Offset, buffer, out bytesWritten)); case 'G': return(TryFormatG(value.DateTime, offset, buffer, out bytesWritten)); default: throw new NotSupportedException(); } }
/// <summary> /// Formats a DateTimeOffset 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> /// <exceptions> /// <remarks> /// Formats supported: /// default 05/25/2017 10:30:15 -08:00 /// G 05/25/2017 10:30:15 /// R Tue, 03 Jan 2017 08:08:05 GMT (RFC 1123) /// l tue, 03 jan 2017 08:08:05 gmt (Lowercase RFC 1123) /// O 2017-06-12T05:30:45.7680000-07:00 (Round-trippable) /// </remarks> /// <cref>System.FormatException</cref> if the format is not valid for this data type. /// </exceptions> public static bool TryFormat(DateTimeOffset value, Span <byte> destination, out int bytesWritten, StandardFormat format = default) { TimeSpan offset = Utf8Constants.NullUtcOffset; char symbol = format.Symbol; if (format.IsDefault) { symbol = 'G'; offset = value.Offset; } switch (symbol) { case 'R': return(TryFormatDateTimeR(value.UtcDateTime, destination, out bytesWritten)); case 'l': return(TryFormatDateTimeL(value.UtcDateTime, destination, out bytesWritten)); case 'O': return(TryFormatDateTimeO(value.DateTime, value.Offset, destination, out bytesWritten)); case 'G': return(TryFormatDateTimeG(value.DateTime, offset, destination, out bytesWritten)); default: return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten)); } }
/// <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(FormattingHelpers.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. { _ = destination[8]; } HexConverter.ToBytesBuffer(guidAsBytes.Byte03, destination, 0, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte02, destination, 2, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte01, destination, 4, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte00, destination, 6, HexConverter.Casing.Lower); if (flags < 0 /* use dash? */) { destination[8] = Dash; destination = destination.Slice(9); } else { destination = destination.Slice(8); } { _ = destination[4]; } HexConverter.ToBytesBuffer(guidAsBytes.Byte05, destination, 0, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte04, destination, 2, HexConverter.Casing.Lower); if (flags < 0 /* use dash? */) { destination[4] = Dash; destination = destination.Slice(5); } else { destination = destination.Slice(4); } { _ = destination[4]; } HexConverter.ToBytesBuffer(guidAsBytes.Byte07, destination, 0, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte06, destination, 2, HexConverter.Casing.Lower); if (flags < 0 /* use dash? */) { destination[4] = Dash; destination = destination.Slice(5); } else { destination = destination.Slice(4); } { _ = destination[4]; } HexConverter.ToBytesBuffer(guidAsBytes.Byte08, destination, 0, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte09, destination, 2, HexConverter.Casing.Lower); if (flags < 0 /* use dash? */) { destination[4] = Dash; destination = destination.Slice(5); } else { destination = destination.Slice(4); } { _ = destination[11]; } // can't hoist bounds check on the final brace (if exists) HexConverter.ToBytesBuffer(guidAsBytes.Byte10, destination, 0, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte11, destination, 2, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte12, destination, 4, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte13, destination, 6, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte14, destination, 8, HexConverter.Casing.Lower); HexConverter.ToBytesBuffer(guidAsBytes.Byte15, destination, 10, HexConverter.Casing.Lower); if ((byte)flags != 0) { destination[12] = (byte)flags; } return(true); }
public static void AppendHttpHeader <TFormatter, T>(this TFormatter formatter, string name, T value, StandardFormat valueFormat = default) where TFormatter : ITextOutput where T : IBufferFormattable { formatter.Append(name); formatter.Append(value, formatter.SymbolTable, valueFormat); formatter.AppendHttpNewLine(); }
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); } byte *pDigits = stackalloc byte[Number.DecimalNumberBufferLength]; Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, 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': { byte *pDigits = stackalloc byte[Number.DecimalNumberBufferLength]; Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, 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); 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': { byte *pDigits = stackalloc byte[Number.DecimalNumberBufferLength]; Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, Number.DecimalNumberBufferLength); Number.DecimalToNumber(ref 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, destination, out bytesWritten, precision, exponentSymbol: (byte)format.Symbol)); } default: return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten)); } }
public static bool TryAppend <TFormatter>(this TFormatter formatter, double value, SymbolTable symbolTable, StandardFormat format = default) where TFormatter : IOutput { if (!CustomFormatter.TryFormat(value, formatter.Buffer, out int bytesWritten, format, symbolTable)) { return(false); } formatter.Advance(bytesWritten); return(true); }
/// <summary> /// Formats a Single 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 bool TryFormat(float value, Span <byte> destination, out int bytesWritten, StandardFormat format = default) { return(TryFormatFloatingPoint <float>(value, destination, out bytesWritten, format)); }
public static void StandardFormatGetHashCodeIsContentBased(StandardFormat f1, StandardFormat f2, bool expectedToBeEqual) { object boxedf1 = f1; object aDifferentBoxedF1 = f1; int h1 = boxedf1.GetHashCode(); int h2 = aDifferentBoxedF1.GetHashCode(); Assert.Equal(h1, h2); }
private static bool TryFormatFloatingPoint <T>(T value, Span <byte> destination, out int bytesWritten, StandardFormat format) where T : IFormattable, ISpanFormattable { Span <char> formatText = stackalloc char[0]; if (!format.IsDefault) { formatText = stackalloc char[StandardFormat.FormatStringLength]; int formatTextLength = format.Format(formatText); formatText = formatText.Slice(0, formatTextLength); } // 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 int formattedLength, formatText, CultureInfo.InvariantCulture)) { utf16Text = stackBuffer.Slice(0, formattedLength); }
public static bool Invoke(T value, Span <byte> destination, out int bytesWritten, StandardFormat format) { return(Instance._formatter(value, destination, out bytesWritten, format)); }
private static bool TryFormatNumber(double value, bool isSingle, Span <byte> buffer, out int bytesWritten, StandardFormat format = default, SymbolTable symbolTable = null) { Precondition.Require(format.Symbol == 'G' || format.Symbol == 'E' || format.Symbol == 'F'); symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; bytesWritten = 0; int written; if (Double.IsNaN(value)) { return(symbolTable.TryEncode(SymbolTable.Symbol.NaN, buffer, out bytesWritten)); } if (Double.IsInfinity(value)) { if (Double.IsNegativeInfinity(value)) { if (!symbolTable.TryEncode(SymbolTable.Symbol.MinusSign, buffer, out written)) { bytesWritten = 0; return(false); } bytesWritten += written; } if (!symbolTable.TryEncode(SymbolTable.Symbol.InfinitySign, buffer.Slice(bytesWritten), out written)) { bytesWritten = 0; return(false); } bytesWritten += written; return(true); } // TODO: the lines below need to be replaced with properly implemented algorithm // the problem is the algorithm is complex, so I am commiting a stub for now var hack = value.ToString(format.Symbol.ToString()); var utf16Bytes = MemoryMarshal.AsBytes(hack.AsSpan()); if (symbolTable == SymbolTable.InvariantUtf8) { var status = Encodings.Utf16.ToUtf8(utf16Bytes, buffer, out int consumed, out bytesWritten); return(status == OperationStatus.Done); } else if (symbolTable == SymbolTable.InvariantUtf16) { bytesWritten = utf16Bytes.Length; if (utf16Bytes.TryCopyTo(buffer)) { return(true); } bytesWritten = 0; return(false); } else { // TODO: This is currently pretty expensive. Can this be done more efficiently? // Note: removing the hack might solve this problem a very different way. var status = Encodings.Utf16.ToUtf8Length(utf16Bytes, out int needed); if (status != OperationStatus.Done) { bytesWritten = 0; return(false); } Span <byte> temp = stackalloc byte[needed]; status = Encodings.Utf16.ToUtf8(utf16Bytes, temp, out int consumed, out written); if (status != OperationStatus.Done) { bytesWritten = 0; return(false); } return(symbolTable.TryEncode(temp, buffer, out consumed, out bytesWritten)); } }
public static bool TryFormat(ulong value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default) => throw null;
private static bool TryFormatInt64(long value, ulong mask, Span <byte> destination, out int bytesWritten, StandardFormat format) { if (format.IsDefault) { return(TryFormatInt64Default(value, destination, out bytesWritten)); } switch (format.Symbol) { case 'G': case 'g': case 'R': case 'r': // treat '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(TryFormatInt64D(value, format.Precision, destination, out bytesWritten)); case 'd': case 'D': return(TryFormatInt64D(value, format.Precision, destination, out bytesWritten)); case 'n': case 'N': return(TryFormatInt64N(value, format.Precision, destination, out bytesWritten)); case 'x': return(TryFormatUInt64X((ulong)value & mask, format.Precision, true, destination, out bytesWritten)); case 'X': return(TryFormatUInt64X((ulong)value & mask, format.Precision, false, destination, out bytesWritten)); default: return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten)); } }
public static bool TryAppend <TFormatter, T>(this TFormatter formatter, T value, StandardFormat format = default) where T : IBufferFormattable where TFormatter : ITextOutput { if (!value.TryFormat(formatter.GetSpan(), out int bytesWritten, format, formatter.SymbolTable)) { return(false); } formatter.Advance(bytesWritten); return(true); }
/// <summary> /// Formats a DateTime 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) 05/25/2017 10:30:15 /// R Tue, 03 Jan 2017 08:08:05 GMT (RFC 1123) /// l tue, 03 jan 2017 08:08:05 gmt (Lowercase RFC 1123) /// O 2017-06-12T05:30:45.7680000-07:00 (Round-trippable) /// </remarks> /// <exceptions> /// <cref>System.FormatException</cref> if the format is not valid for this data type. /// </exceptions> public static bool TryFormat(DateTime value, Span <byte> destination, out int bytesWritten, StandardFormat format = default) { char symbol = FormattingHelpers.GetSymbolOrDefault(format, 'G'); switch (symbol) { case 'R': return(TryFormatDateTimeR(value, destination, out bytesWritten)); case 'l': return(TryFormatDateTimeL(value, destination, out bytesWritten)); case 'O': return(TryFormatDateTimeO(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten)); case 'G': return(TryFormatDateTimeG(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten)); default: return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten)); } }
public static bool TryAppend <TFormatter>(this TFormatter formatter, DateTime value, StandardFormat format = default) where TFormatter : ITextOutput { if (!CustomFormatter.TryFormat(value, formatter.GetSpan(), out int bytesWritten, format, formatter.SymbolTable)) { return(false); } formatter.Advance(bytesWritten); return(true); }
/// <summary> /// /// </summary> /// <param name="value"></param> /// <param name="buffer"></param> /// <param name="bytesWritten"></param> /// <param name="format">only 'G' format is supported</param> /// <returns></returns> public static bool TryFormat(decimal value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default) { if (format.IsDefault) { format = 'G'; } else if (format.Symbol != 'G') { throw new FormatException(); } var text = value.ToString("G"); if (MemoryMarshal.AsBytes(text.AsSpan()).TryCopyTo(buffer)) { bytesWritten = text.Length * 2; return(true); } bytesWritten = 0; return(false); }