File: src\libraries\System.Private.CoreLib\src\System\ParseNumbers.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// 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.Runtime.CompilerServices;
 
namespace System
{
    /// <summary>Methods for parsing numbers and strings.</summary>
    internal static class ParseNumbers
    {
        internal const int TreatAsUnsigned = 0x0200;
        internal const int TreatAsI1 = 0x0400;
        internal const int TreatAsI2 = 0x0800;
        internal const int IsTight = 0x1000;
 
        public static long StringToLong(ReadOnlySpan<char> s, int radix, int flags)
        {
            int pos = 0;
            return StringToLong(s, radix, flags, ref pos);
        }
 
        public static long StringToLong(ReadOnlySpan<char> 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_IndexMustBeLess);
 
            // Get rid of the whitespace and then check that we've still got some digits to parse.
            if ((flags & IsTight) == 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<long>();
 
            if (r == 10)
            {
                result *= sign;
            }
 
            return result;
        }
 
        public static int StringToInt(ReadOnlySpan<char> s, int radix, int flags)
        {
            int pos = 0;
            return StringToInt(s, radix, flags, ref pos);
        }
 
        public static int StringToInt(ReadOnlySpan<char> 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_IndexMustBeLess);
 
            // Get rid of the whitespace and then check that we've still got some digits to parse.
            if ((flags & IsTight) == 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<sbyte>();
            }
            else if ((flags & TreatAsI2) != 0)
            {
                if ((uint)result > 0xFFFF)
                    Number.ThrowOverflowException<short>();
            }
            else if ((uint)result == 0x80000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0))
            {
                Number.ThrowOverflowException<int>();
            }
 
            if (r == 10)
            {
                result *= sign;
            }
 
            return result;
        }
 
        private static void EatWhiteSpace(ReadOnlySpan<char> 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<char> 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<long>();
                    }
 
                    result = result * (ulong)radix + (ulong)value;
                    i++;
                }
 
                if ((long)result < 0 && result != 0x8000000000000000)
                {
                    Number.ThrowOverflowException<long>();
                }
            }
            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<ulong>();
                    }
 
                    ulong temp = result * (ulong)radix + (ulong)value;
 
                    if (temp < result) // this means overflow as well
                    {
                        Number.ThrowOverflowException<ulong>();
                    }
 
                    result = temp;
                    i++;
                }
            }
 
            return (long)result;
        }
 
        private static int GrabInts(int radix, ReadOnlySpan<char> 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<int>();
                    }
                    result = result * (uint)radix + (uint)value;
                    i++;
                }
                if ((int)result < 0 && result != 0x80000000)
                {
                    Number.ThrowOverflowException<int>();
                }
            }
            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<uint>();
                    }
 
                    uint temp = result * (uint)radix + (uint)value;
 
                    if (temp < result) // this means overflow as well
                    {
                        Number.ThrowOverflowException<uint>();
                    }
 
                    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;
        }
    }
}