/// <summary> /// Integer /// Handles the 16,32bit signed and unsigned integers /// 64bit are handled separately /// </summary> private static void Integer(ref StackBuffer buffer, StackBuffer format, TypeCode type, long value, bool signed) { var negSign = signed && value < 0; var fmt = ParseFormatSpecifier(format, out var digits); var fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison if (fmtUpper == 'G' && digits < 1 || fmtUpper == 'D') { EvalInt(ref buffer, (ulong)Math.Abs(value), digits, 10, negSign); } else if (fmtUpper == 'X') { if (type == TypeCode.Int16) { if (value < 0) { EvalInt(ref buffer, (uint)(value & 0x0000FFFF), digits, 16, false); return; } } if (type == TypeCode.Int32) { if (value < 0) { EvalInt(ref buffer, (uint)(value & 0xFFFFFFFF), digits, 16, false); return; } } EvalInt(ref buffer, (ulong)Math.Abs(value), digits, 16, negSign); } }
/// <summary> /// EvalArg /// Entry point for evaluating the generic arguments /// </summary> private static void EvalArg <T>(ref StackBuffer buffer, StackBuffer format, T arg) where T : IConvertible { switch (arg?.GetTypeCode()) { case TypeCode.Int16: { Integer(ref buffer, format, arg.GetTypeCode(), arg.ToInt16(NumberFormatInfo.CurrentInfo), true); break; } case TypeCode.Int32: { Integer(ref buffer, format, arg.GetTypeCode(), arg.ToInt32(NumberFormatInfo.CurrentInfo), true); break; } case TypeCode.Int64: { Long(ref buffer, format, arg.GetTypeCode(), arg.ToInt64(NumberFormatInfo.CurrentInfo), true); break; } case TypeCode.UInt16: { Integer(ref buffer, format, arg.GetTypeCode(), arg.ToUInt16(NumberFormatInfo.CurrentInfo), false); break; } case TypeCode.UInt32: { Integer(ref buffer, format, arg.GetTypeCode(), arg.ToUInt32(NumberFormatInfo.CurrentInfo), false); break; } case TypeCode.UInt64: { ULong(ref buffer, format, arg.GetTypeCode(), arg.ToUInt64(NumberFormatInfo.CurrentInfo), false); break; } case TypeCode.Single: { FloatingPoint(ref buffer, format, arg.ToSingle(NumberFormatInfo.CurrentInfo)); break; } case TypeCode.Double: { FloatingPoint(ref buffer, format, arg.ToDouble(NumberFormatInfo.CurrentInfo)); break; } case TypeCode.String: { buffer.Append(arg.ToString()); break; } } }
/// <summary> /// EvalULong /// Converts 64bit signed and unsigned ints to ascii /// </summary> private static void EvalULong(ref StackBuffer buffer, ulong longVal, int digits, long baseVal, bool negSign) { // add the characters in reverse order do { // Lookup from static char array, to cover hex values too buffer.Append(asciiDigits[longVal % (ulong)baseVal]); longVal /= (ulong)baseVal; digits--; } while (longVal != 0); Pad(ref buffer, digits, negSign); }
// Set the string using the contents of a StackBuffer public void SetStackBuffer(int destPos, StackBuffer buffer) { var newLength = destPos + buffer.Count; if (destPos + buffer.Count > Capacity) { throw new ArgumentOutOfRangeException(); } SetLength(newLength); for (var i = destPos; i < newLength; i++) { this[i] = buffer[i]; } }
private static void Pad(ref StackBuffer buffer, int digits, bool negSign) { // add preceding zeros while (digits-- > 0) { buffer.Append('0'); } // add sign if (negSign) { buffer.Append('-'); } // reverse it into the correct order buffer.Reverse(); }
/// <summary> /// Unsigned 64bit ints /// </summary> private static void ULong(ref StackBuffer buffer, StackBuffer format, TypeCode type, ulong value, bool signed) { var negSign = signed && value < 0; var fmt = ParseFormatSpecifier(format, out var digits); var fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison if (fmtUpper == 'G' && digits < 1 || fmtUpper == 'D') { EvalULong(ref buffer, value, digits, 10, negSign); } else if (fmtUpper == 'X') { EvalULong(ref buffer, value, digits, 16, negSign); } }
/// <summary> /// EvalInt /// Converts 16,32bit signed and unsigned ints to ascii /// </summary> private static void EvalInt(ref StackBuffer buffer, ulong intVal, int digits, ulong baseVal, bool negSign) { // add the characters in reverse order do { // Lookup from static char array, to cover hex values too buffer.Append(asciiDigits[intVal % baseVal]); intVal /= baseVal; digits--; } while (intVal != 0); // add preceding zeros while (digits-- > 0) { buffer.Append('0'); } // add sign if (negSign) { buffer.Append('-'); } // reverse it into the correct order buffer.Reverse(); }
public static void Convert(ref StackBuffer buffer, double f, int precision) { if (f > ulong.MaxValue) { buffer.Append("Out-of-range"); return; } ulong intPart; ulong baseVal = 10; // baseVal has to be the same type as the integer part // check precision bounds if (precision > MAX_PRECISION) { precision = MAX_PRECISION; } // sign stuff if (f < 0) { f = -f; buffer.Append('-'); } if (precision < 0) // negative precision == automatic precision guess { if (f < 1.0) { precision = 6; } else if (f < 10.0) { precision = 5; } else if (f < 100.0) { precision = 4; } else if (f < 1000.0) { precision = 3; } else if (f < 10000.0) { precision = 2; } else if (f < 100000.0) { precision = 1; } else { precision = 0; } } // round value according the precision if (precision > 0) { f += rounders[precision]; } // integer part... intPart = (ulong)Math.Truncate(f); f -= intPart; if (intPart == 0) { buffer.Append('0'); } else { var tempBuffer = new StackBuffer(stackalloc char[MAX_PRECISION]); // convert (reverse order) while (intPart != 0) { tempBuffer.Append((char)('0' + intPart % baseVal)); intPart /= baseVal; } tempBuffer.Reverse(); for (var i = 0; i < tempBuffer.Count; i++) { buffer.Append(tempBuffer[i]); } } // decimal part if (precision != 0) { // place decimal point buffer.Append('.'); // convert while (precision-- != 0) { f *= 10.0; var c = (char)f; buffer.Append((char)('0' + c)); f -= c; } } }
/// <summary> /// ParseFormatSpecifier /// https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs /// </summary> /// <returns></returns> private static char ParseFormatSpecifier(StackBuffer format, out int digits) { char c = default; if (format.Length > 0) { // If the format begins with a symbol, see if it's a standard format // with or without a specified number of digits. c = format[0]; if ((uint)(c - 'A') <= 'Z' - 'A' || (uint)(c - 'a') <= 'z' - 'a') { // Fast path for sole symbol, e.g. "D" if (format.Length == 1) { digits = -1; return(c); } if (format.Length == 2) { // Fast path for symbol and single digit, e.g. "X4" var d = format[1] - '0'; if ((uint)d < 10) { digits = d; return(c); } } else if (format.Length == 3) { // Fast path for symbol and double digit, e.g. "F12" int d1 = format[1] - '0', d2 = format[2] - '0'; if ((uint)d1 < 10 && (uint)d2 < 10) { digits = d1 * 10 + d2; return(c); } } // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99, // but it can begin with any number of 0s, and thus we may need to check more than two // digits. Further, for compat, we need to stop when we hit a null char. var n = 0; var i = 1; while (i < format.Length && (uint)format[i] - '0' < 10 && n < 10) { n = n * 10 + format[i++] - '0'; } // If we're at the end of the digits rather than having stopped because we hit something // other than a digit or overflowed, return the standard format info. if (i == format.Length || format[i] == '\0') { digits = n; return(c); } } } // Default empty format to be "G"; custom format is signified with '\0'. digits = -1; return(format.Length == 0 || c == '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it. 'G' : '\0'); }
/// <summary> /// FloatingPoint /// Handles single and double precision floating point values /// </summary> private static void FloatingPoint(ref StackBuffer buffer, StackBuffer format, double value) { var fmt = ParseFormatSpecifier(format, out var digits); ftoa.Convert(ref buffer, value, digits); }