// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace System
{
/// Methods for parsing numbers and strings.
internal static class ParseNumbers
{
internal const int LeftAlign = 0x0001;
internal const int RightAlign = 0x0004;
internal const int PrefixSpace = 0x0008;
internal const int PrintSign = 0x0010;
internal const int PrintBase = 0x0020;
internal const int PrintAsI1 = 0x0040;
internal const int PrintAsI2 = 0x0080;
internal const int PrintAsI4 = 0x0100;
internal const int TreatAsUnsigned = 0x0200;
internal const int TreatAsI1 = 0x0400;
internal const int TreatAsI2 = 0x0800;
internal const int IsTight = 0x1000;
internal const int NoSpace = 0x2000;
internal const int PrintRadixBase = 0x4000;
private const int MinRadix = 2;
private const int MaxRadix = 36;
public static unsafe long StringToLong(ReadOnlySpan s, int radix, int flags)
{
int pos = 0;
return StringToLong(s, radix, flags, ref pos);
}
public static long StringToLong(ReadOnlySpan s, int radix, int flags, ref int currPos)
{
int i = currPos;
// Do some radix checking.
// A radix of -1 says to use whatever base is spec'd on the number.
// Parse in Base10 until we figure out what the base actually is.
int r = (-1 == radix) ? 10 : radix;
if (r != 2 && r != 10 && r != 8 && r != 16)
throw new ArgumentException(SR.Arg_InvalidBase, nameof(radix));
int length = s.Length;
if (i < 0 || i >= length)
throw new ArgumentOutOfRangeException(SR.ArgumentOutOfRange_Index);
// Get rid of the whitespace and then check that we've still got some digits to parse.
if (((flags & IsTight) == 0) && ((flags & NoSpace) == 0))
{
EatWhiteSpace(s, ref i);
if (i == length)
throw new FormatException(SR.Format_EmptyInputString);
}
// Check for a sign
int sign = 1;
if (s[i] == '-')
{
if (r != 10)
throw new ArgumentException(SR.Arg_CannotHaveNegativeValue);
if ((flags & TreatAsUnsigned) != 0)
throw new OverflowException(SR.Overflow_NegativeUnsigned);
sign = -1;
i++;
}
else if (s[i] == '+')
{
i++;
}
if ((radix == -1 || radix == 16) && (i + 1 < length) && s[i] == '0')
{
if (s[i + 1] == 'x' || s[i + 1] == 'X')
{
r = 16;
i += 2;
}
}
int grabNumbersStart = i;
long result = GrabLongs(r, s, ref i, (flags & TreatAsUnsigned) != 0);
// Check if they passed us a string with no parsable digits.
if (i == grabNumbersStart)
throw new FormatException(SR.Format_NoParsibleDigits);
if ((flags & IsTight) != 0)
{
//If we've got effluvia left at the end of the string, complain.
if (i < length)
throw new FormatException(SR.Format_ExtraJunkAtEnd);
}
// Put the current index back into the correct place.
currPos = i;
// Return the value properly signed.
if ((ulong)result == 0x8000000000000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0))
Number.ThrowOverflowException(TypeCode.Int64);
if (r == 10)
{
result *= sign;
}
return result;
}
public static int StringToInt(ReadOnlySpan s, int radix, int flags)
{
int pos = 0;
return StringToInt(s, radix, flags, ref pos);
}
public static int StringToInt(ReadOnlySpan s, int radix, int flags, ref int currPos)
{
// They're requied to tell me where to start parsing.
int i = currPos;
// Do some radix checking.
// A radix of -1 says to use whatever base is spec'd on the number.
// Parse in Base10 until we figure out what the base actually is.
int r = (-1 == radix) ? 10 : radix;
if (r != 2 && r != 10 && r != 8 && r != 16)
throw new ArgumentException(SR.Arg_InvalidBase, nameof(radix));
int length = s.Length;
if (i < 0 || i >= length)
throw new ArgumentOutOfRangeException(SR.ArgumentOutOfRange_Index);
// Get rid of the whitespace and then check that we've still got some digits to parse.
if (((flags & IsTight) == 0) && ((flags & NoSpace) == 0))
{
EatWhiteSpace(s, ref i);
if (i == length)
throw new FormatException(SR.Format_EmptyInputString);
}
// Check for a sign
int sign = 1;
if (s[i] == '-')
{
if (r != 10)
throw new ArgumentException(SR.Arg_CannotHaveNegativeValue);
if ((flags & TreatAsUnsigned) != 0)
throw new OverflowException(SR.Overflow_NegativeUnsigned);
sign = -1;
i++;
}
else if (s[i] == '+')
{
i++;
}
// Consume the 0x if we're in an unknown base or in base-16.
if ((radix == -1 || radix == 16) && (i + 1 < length) && s[i] == '0')
{
if (s[i + 1] == 'x' || s[i + 1] == 'X')
{
r = 16;
i += 2;
}
}
int grabNumbersStart = i;
int result = GrabInts(r, s, ref i, ((flags & TreatAsUnsigned) != 0));
// Check if they passed us a string with no parsable digits.
if (i == grabNumbersStart)
throw new FormatException(SR.Format_NoParsibleDigits);
if ((flags & IsTight) != 0)
{
// If we've got effluvia left at the end of the string, complain.
if (i < length)
throw new FormatException(SR.Format_ExtraJunkAtEnd);
}
// Put the current index back into the correct place.
currPos = i;
// Return the value properly signed.
if ((flags & TreatAsI1) != 0)
{
if ((uint)result > 0xFF)
Number.ThrowOverflowException(TypeCode.SByte);
}
else if ((flags & TreatAsI2) != 0)
{
if ((uint)result > 0xFFFF)
Number.ThrowOverflowException(TypeCode.Int16);
}
else if ((uint)result == 0x80000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0))
{
Number.ThrowOverflowException(TypeCode.Int32);
}
if (r == 10)
{
result *= sign;
}
return result;
}
public static string IntToString(int n, int radix, int width, char paddingChar, int flags)
{
Span buffer = stackalloc char[66]; // Longest possible string length for an integer in binary notation with prefix
if (radix < MinRadix || radix > MaxRadix)
throw new ArgumentException(SR.Arg_InvalidBase, nameof(radix));
// If the number is negative, make it positive and remember the sign.
// If the number is MIN_VALUE, this will still be negative, so we'll have to
// special case this later.
bool isNegative = false;
uint l;
if (n < 0)
{
isNegative = true;
// For base 10, write out -num, but other bases write out the
// 2's complement bit pattern
l = (10 == radix) ? (uint)-n : (uint)n;
}
else
{
l = (uint)n;
}
// The conversion to a uint will sign extend the number. In order to ensure
// that we only get as many bits as we expect, we chop the number.
if ((flags & PrintAsI1) != 0)
{
l &= 0xFF;
}
else if ((flags & PrintAsI2) != 0)
{
l &= 0xFFFF;
}
// Special case the 0.
int index;
if (0 == l)
{
buffer[0] = '0';
index = 1;
}
else
{
index = 0;
for (int i = 0; i < buffer.Length; i++) // for(...;i buffer = stackalloc char[67]; // Longest possible string length for an integer in binary notation with prefix
if (radix < MinRadix || radix > MaxRadix)
throw new ArgumentException(SR.Arg_InvalidBase, nameof(radix));
//If the number is negative, make it positive and remember the sign.
ulong ul;
bool isNegative = false;
if (n < 0)
{
isNegative = true;
// For base 10, write out -num, but other bases write out the
// 2's complement bit pattern
ul = (10 == radix) ? (ulong)(-n) : (ulong)n;
}
else
{
ul = (ulong)n;
}
if ((flags & PrintAsI1) != 0)
{
ul = ul & 0xFF;
}
else if ((flags & PrintAsI2) != 0)
{
ul = ul & 0xFFFF;
}
else if ((flags & PrintAsI4) != 0)
{
ul = ul & 0xFFFFFFFF;
}
//Special case the 0.
int index;
if (0 == ul)
{
buffer[0] = '0';
index = 1;
}
else
{
index = 0;
for (int i = 0; i < buffer.Length; i++) // for loop instead of do{...}while(l!=0) to help JIT eliminate span bounds checks
{
ulong div = ul / (ulong)radix; // TODO https://github.com/dotnet/coreclr/issues/3439
int charVal = (int)(ul - (div * (ulong)radix));
ul = div;
buffer[i] = (charVal < 10) ?
(char)(charVal + '0') :
(char)(charVal + 'a' - 10);
if (ul == 0)
{
index = i + 1;
break;
}
}
Debug.Assert(ul == 0, $"Expected {ul} == 0");
}
//If they want the base, append that to the string (in reverse order)
if (radix != 10 && ((flags & PrintBase) != 0))
{
if (16 == radix)
{
buffer[index++] = 'x';
buffer[index++] = '0';
}
else if (8 == radix)
{
buffer[index++] = '0';
}
else if ((flags & PrintRadixBase) != 0)
{
buffer[index++] = '#';
buffer[index++] = (char)((radix % 10) + '0');
buffer[index++] = (char)((radix / 10) + '0');
}
}
if (10 == radix)
{
//If it was negative, append the sign.
if (isNegative)
{
buffer[index++] = '-';
}
//else if they requested, add the '+';
else if ((flags & PrintSign) != 0)
{
buffer[index++] = '+';
}
//If they requested a leading space, put it on.
else if ((flags & PrefixSpace) != 0)
{
buffer[index++] = ' ';
}
}
// Figure out the size of and allocate the resulting string
string result = string.FastAllocateString(Math.Max(width, index));
unsafe
{
// Put the characters into the string in reverse order.
// Fill the remaining space, if there is any, with the correct padding character.
fixed (char* resultPtr = result)
{
char* p = resultPtr;
int padding = result.Length - index;
if ((flags & LeftAlign) != 0)
{
for (int i = 0; i < padding; i++)
{
*p++ = paddingChar;
}
for (int i = 0; i < index; i++)
{
*p++ = buffer[index - i - 1];
}
}
else
{
for (int i = 0; i < index; i++)
{
*p++ = buffer[index - i - 1];
}
for (int i = 0; i < padding; i++)
{
*p++ = paddingChar;
}
}
Debug.Assert((p - resultPtr) == result.Length, $"Expected {p - resultPtr} == {result.Length}");
}
}
return result;
}
private static void EatWhiteSpace(ReadOnlySpan s, ref int i)
{
int localIndex = i;
for (; localIndex < s.Length && char.IsWhiteSpace(s[localIndex]); localIndex++);
i = localIndex;
}
private static long GrabLongs(int radix, ReadOnlySpan s, ref int i, bool isUnsigned)
{
ulong result = 0;
ulong maxVal;
// Allow all non-decimal numbers to set the sign bit.
if (radix == 10 && !isUnsigned)
{
maxVal = 0x7FFFFFFFFFFFFFFF / 10;
// Read all of the digits and convert to a number
while (i < s.Length && IsDigit(s[i], radix, out int value))
{
// Check for overflows - this is sufficient & correct.
if (result > maxVal || ((long)result) < 0)
{
Number.ThrowOverflowException(TypeCode.Int64);
}
result = result * (ulong)radix + (ulong)value;
i++;
}
if ((long)result < 0 && result != 0x8000000000000000)
{
Number.ThrowOverflowException(TypeCode.Int64);
}
}
else
{
Debug.Assert(radix == 2 || radix == 8 || radix == 10 || radix == 16);
maxVal =
radix == 10 ? 0xffffffffffffffff / 10 :
radix == 16 ? 0xffffffffffffffff / 16 :
radix == 8 ? 0xffffffffffffffff / 8 :
0xffffffffffffffff / 2;
// Read all of the digits and convert to a number
while (i < s.Length && IsDigit(s[i], radix, out int value))
{
// Check for overflows - this is sufficient & correct.
if (result > maxVal)
{
Number.ThrowOverflowException(TypeCode.UInt64);
}
ulong temp = result * (ulong)radix + (ulong)value;
if (temp < result) // this means overflow as well
{
Number.ThrowOverflowException(TypeCode.UInt64);
}
result = temp;
i++;
}
}
return (long)result;
}
private static int GrabInts(int radix, ReadOnlySpan s, ref int i, bool isUnsigned)
{
uint result = 0;
uint maxVal;
// Allow all non-decimal numbers to set the sign bit.
if (radix == 10 && !isUnsigned)
{
maxVal = (0x7FFFFFFF / 10);
// Read all of the digits and convert to a number
while (i < s.Length && IsDigit(s[i], radix, out int value))
{
// Check for overflows - this is sufficient & correct.
if (result > maxVal || (int)result < 0)
{
Number.ThrowOverflowException(TypeCode.Int32);
}
result = result * (uint)radix + (uint)value;
i++;
}
if ((int)result < 0 && result != 0x80000000)
{
Number.ThrowOverflowException(TypeCode.Int32);
}
}
else
{
Debug.Assert(radix == 2 || radix == 8 || radix == 10 || radix == 16);
maxVal =
radix == 10 ? 0xffffffff / 10 :
radix == 16 ? 0xffffffff / 16 :
radix == 8 ? 0xffffffff / 8 :
0xffffffff / 2;
// Read all of the digits and convert to a number
while (i < s.Length && IsDigit(s[i], radix, out int value))
{
// Check for overflows - this is sufficient & correct.
if (result > maxVal)
{
Number.ThrowOverflowException(TypeCode.UInt32);
}
uint temp = result * (uint)radix + (uint)value;
if (temp < result) // this means overflow as well
{
Number.ThrowOverflowException(TypeCode.UInt32);
}
result = temp;
i++;
}
}
return (int)result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsDigit(char c, int radix, out int result)
{
int tmp;
if ((uint)(c - '0') <= 9)
{
result = tmp = c - '0';
}
else if ((uint)(c - 'A') <= 'Z' - 'A')
{
result = tmp = c - 'A' + 10;
}
else if ((uint)(c - 'a') <= 'z' - 'a')
{
result = tmp = c - 'a' + 10;
}
else
{
result = -1;
return false;
}
return tmp < radix;
}
}
}