|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace System.Buffers
{
/// <summary>
/// Represents a standard formatting string without using an actual String. A StandardFormat consists of a character (such as 'G', 'D' or 'X')
/// and an optional precision ranging from 0..99, or the special value NoPrecision.
/// </summary>
public readonly struct StandardFormat : IEquatable<StandardFormat>
{
/// <summary>
/// Precision values for format that don't use a precision, or for when the precision is to be unspecified.
/// </summary>
public const byte NoPrecision = byte.MaxValue;
/// <summary>
/// The maximum valid precision value.
/// </summary>
public const byte MaxPrecision = 99;
private readonly byte _format;
private readonly byte _precision;
/// <summary>
/// The character component of the format.
/// </summary>
public char Symbol => (char)_format;
/// <summary>
/// The precision component of the format. Ranges from 0..9 or the special value NoPrecision.
/// </summary>
public byte Precision => _precision;
/// <summary>
/// true if Precision is a value other than NoPrecision
/// </summary>
public bool HasPrecision => _precision != NoPrecision;
/// <summary>Gets the precision if one was specified; otherwise, 0.</summary>
internal byte PrecisionOrZero => _precision != NoPrecision ? _precision : (byte)0;
/// <summary>
/// true if the StandardFormat == default(StandardFormat)
/// </summary>
public bool IsDefault => (_format | _precision) == 0;
/// <summary>
/// Create a StandardFormat.
/// </summary>
/// <param name="symbol">A type-specific formatting character such as 'G', 'D' or 'X'</param>
/// <param name="precision">An optional precision ranging from 0..9 or the special value NoPrecision (the default)</param>
public StandardFormat(char symbol, byte precision = NoPrecision)
{
if (precision != NoPrecision && precision > MaxPrecision)
ThrowHelper.ThrowArgumentOutOfRangeException_PrecisionTooLarge();
if (symbol != (byte)symbol)
ThrowHelper.ThrowArgumentOutOfRangeException_SymbolDoesNotFit();
_format = (byte)symbol;
_precision = precision;
}
/// <summary>
/// Converts a character to a StandardFormat using the NoPrecision precision.
/// </summary>
public static implicit operator StandardFormat(char symbol) => new StandardFormat(symbol);
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Char}"/> into a StandardFormat
/// </summary>
public static StandardFormat Parse([StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan<char> format)
{
ParseHelper(format, out StandardFormat standardFormat, throws: true);
return standardFormat;
}
/// <summary>
/// Converts a classic .NET format string into a StandardFormat
/// </summary>
public static StandardFormat Parse([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) => format == null ? default : Parse(format.AsSpan());
/// <summary>
/// Tries to convert a <see cref="ReadOnlySpan{Char}"/> into a StandardFormat. A return value indicates whether the conversion succeeded or failed.
/// </summary>
public static bool TryParse([StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan<char> format, out StandardFormat result)
{
return ParseHelper(format, out result);
}
private static bool ParseHelper(ReadOnlySpan<char> format, out StandardFormat standardFormat, bool throws = false)
{
standardFormat = default;
if (format.Length == 0)
return true;
char symbol = format[0];
byte precision;
if (format.Length == 1)
{
precision = NoPrecision;
}
else
{
uint parsedPrecision = 0;
for (int srcIndex = 1; srcIndex < format.Length; srcIndex++)
{
uint digit = format[srcIndex] - 48u; // '0'
if (digit > 9)
{
return throws ? throw new FormatException(SR.Format(SR.Argument_CannotParsePrecision, MaxPrecision)) : false;
}
parsedPrecision = parsedPrecision * 10 + digit;
if (parsedPrecision > MaxPrecision)
{
return throws ? throw new FormatException(SR.Format(SR.Argument_PrecisionTooLarge, MaxPrecision)) : false;
}
}
precision = (byte)parsedPrecision;
}
standardFormat = new StandardFormat(symbol, precision);
return true;
}
/// <summary>
/// Returns true if both the Symbol and Precision are equal.
/// </summary>
public override bool Equals([NotNullWhen(true)] object? obj) => obj is StandardFormat other && Equals(other);
/// <summary>
/// Compute a hash code.
/// </summary>
public override int GetHashCode() => _format.GetHashCode() ^ _precision.GetHashCode();
/// <summary>
/// Returns true if both the Symbol and Precision are equal.
/// </summary>
public bool Equals(StandardFormat other) => _format == other._format && _precision == other._precision;
/// <summary>
/// Returns the format in classic .NET format.
/// </summary>
public override string ToString() => new string(Format(stackalloc char[FormatStringLength]));
/// <summary>The exact buffer length required by <see cref="Format"/>.</summary>
internal const int FormatStringLength = 3;
/// <summary>
/// Formats the format in classic .NET format.
/// </summary>
internal Span<char> Format(Span<char> destination)
{
Debug.Assert(destination.Length == FormatStringLength);
char symbol = Symbol;
if (symbol != default && destination.Length == FormatStringLength)
{
destination[0] = symbol;
uint precision = Precision;
if (precision != NoPrecision)
{
// Note that Precision is stored as a byte, so in theory it could contain
// values > MaxPrecision (99). But all supported mechanisms for creating a
// StandardFormat limit values to being <= MaxPrecision, so the only way a value
// could be larger than that is if unsafe code or the equivalent were used
// to force a larger invalid value in, in which case we don't need to
// guarantee such an invalid value is properly roundtripped through here;
// we just need to make sure things aren't corrupted further.
if (precision >= 10)
{
uint div;
(div, precision) = Math.DivRem(precision, 10);
destination[1] = (char)('0' + div % 10);
destination[2] = (char)('0' + precision);
return destination;
}
destination[1] = (char)('0' + precision);
return destination.Slice(0, 2);
}
return destination.Slice(0, 1);
}
return default;
}
/// <summary>
/// Returns true if both the Symbol and Precision are equal.
/// </summary>
public static bool operator ==(StandardFormat left, StandardFormat right) => left.Equals(right);
/// <summary>
/// Returns false if both the Symbol and Precision are equal.
/// </summary>
public static bool operator !=(StandardFormat left, StandardFormat right) => !left.Equals(right);
}
}
|