/// <summary>
        /// Parses a (big) decimal from a string.
        /// </summary>
        /// <remarks>
        /// Number format: (given in pseudo-regex, no whitespace allowed)
        /// {+|-}? [0-9]* (\. [0-9]*)? (E {+|-}? [0-9]+)?
        /// All of the parts are optional, but there must be at least one digit, and if the 'E' (case insensitive) exists there must be at least one digit before and at least one digit after the E.
        ///
        /// Two passes are made over the string; one to validate and split it into its parts, and one to actually parts the different parts.
        ///
        /// The exponent part is limited to the int range; an overflow will be thrown for numbers above or below that range.
        /// The digits after the decimal point are limited to the ushort range, i.e. 65535 digits. An overflow will be thrown for more digits.
        ///
        /// If (the nubmer of digits after the decimal point - the exponent) is less than -ushort.MAX_VALUE, then precision will be lost. (e.g. 1E-70000 == 0, and 123.456E-65536 == 120E65536)
        /// </remarks>
        /// <exception cref="ArgumentNullException">The given string is null</exception>
        /// <exception cref="FormatException">The number could not be parsed because it had a bad format or invalid characters</exception>
        /// <exception cref="OverflowException">One of the parts was over its limit: the exponent or the number of digits after the decimal point</exception>
        public static BigDecimal Parse(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException("str", "BigDecimal.Parse: Cannot parse null");
            }

            // first, we go over the string, separate it into parts.
            // At this stage, we create:
            //		valueBuilder: Contains a string that represents the value (should be parsed using BigInteger), e.g. for 1234.5678E-17, will contain "12345678".
            //		exponentBuilder: Is null if no exponent exists in the original string, or contains a string that represents that exponent otherwise, e.g. for 1234.5678E-17, will contain "-17".
            //		scale: Contains the scale from the original number (before the exponent was applied), e.g. for 1234.5678E-17, will contain 4.
            // The second stage should parse the results. In our example of 1234.5678E-17, the final result should be value=12345678 and scale=4-(-17)=21.
            ushort        scale           = 0;
            StringBuilder valueBuilder    = new StringBuilder();
            StringBuilder exponentBuilder = null;


            ParseState state = ParseState.Start;

            // non-trivial things that are using in multiple cases
            Action <char> formatException = c => { throw new FormatException("BigDecimal.Parse: invalid character '" + c + "' in: " + str); };
            Action        startExponent   = () => { exponentBuilder = new StringBuilder(); state = ParseState.E; };

            foreach (char c in str)
            {
                switch (state)
                {
                case ParseState.Start:
                    if (char.IsDigit(c) || c == '-' || c == '+')
                    {
                        state = ParseState.Integer;
                        valueBuilder.Append(c);
                    }
                    else if (c == '.')
                    {
                        state = ParseState.Decimal;
                    }
                    else
                    {
                        formatException(c);
                    }
                    break;

                case ParseState.Integer:
                    if (char.IsDigit(c))
                    {
                        valueBuilder.Append(c);
                    }
                    else if (c == '.')
                    {
                        state = ParseState.Decimal;
                    }
                    else if (c == 'e' || c == 'E')
                    {
                        startExponent();
                    }
                    else
                    {
                        formatException(c);
                    }
                    break;

                case ParseState.Decimal:
                    if (char.IsDigit(c))
                    {
                        // checked so that an overflow is thrown for too much precision
                        checked { scale++; }
                        valueBuilder.Append(c);
                    }
                    else if (c == 'e' || c == 'E')
                    {
                        startExponent();
                    }
                    else
                    {
                        formatException(c);
                    }
                    break;

                case ParseState.E:
                    if (char.IsDigit(c) || c == '-' || c == '+')
                    {
                        state = ParseState.Exponent;
                        exponentBuilder.Append(c);
                    }
                    else
                    {
                        formatException(c);
                    }
                    break;

                case ParseState.Exponent:
                    if (char.IsDigit(c))
                    {
                        exponentBuilder.Append(c);
                    }
                    else
                    {
                        formatException(c);
                    }
                    break;
                }
            }

            if (valueBuilder.Length == 0 ||
                (valueBuilder.Length == 1 && !char.IsDigit(valueBuilder[0])))
            {
                // the value doesn't have any digit (one character could be the sign)
                throw new FormatException("BigDecimal.Parse: string didn't contain a value: \"" + str + "\"");
            }

            if (exponentBuilder != null &&
                (exponentBuilder.Length == 0 ||
                 (valueBuilder.Length == 1 && !char.IsDigit(valueBuilder[0]))))
            {
                // the scale builder exists but is empty, meaning there was an 'e' in the number, but no digits afterwards (one character could be the sign)
                throw new FormatException("BigDecimal.Parse: string contained an 'E' but no exponent value: \"" + str + "\"");
            }

            BigInteger value = BigInteger.Parse(valueBuilder.ToString());

            if (exponentBuilder == null)
            {
                // simple case with no exponent
            }
            else
            {
                // we need to correct the scale to match the given exponent
                // Note: The scale goes downwards (i.e. a large scale means more precision) while the exponent goes up (i.e. a large exponent means the number is larger)
                int exponent = int.Parse(exponentBuilder.ToString());
                if (exponent > 0)
                {
                    if (exponent <= scale)
                    {
                        // relatively simply case; decrease the scale by the exponent (e.g. 1.2e1 would have a scale of 1 and exponent of 1 resulting in 12 with scale of 0)
                        scale -= (ushort)exponent;
                    }
                    else
                    {
                        // scale would be negative; increase the actual value to represent that (remember, scale is only used for places after the decimal point)
                        exponent -= scale;
                        scale     = 0;
                        value    *= BigInteger.Pow(10, exponent);
                    }
                }
                else if (exponent < 0)
                {
                    exponent = (-exponent) + scale;
                    if (exponent <= ushort.MaxValue)
                    {
                        // agian, relatively simple case; increate the scale by the (negated) exponent (e.g. 1.2e-1 would have a scale of 1 and an exponent of -1 resulting in 12 with scale of 2, i.e. 0.12)
                        scale = (ushort)exponent;
                    }
                    else
                    {
                        // scale would overflow; lose some precision instead by dividing the value (integer truncating division)
                        scale  = ushort.MaxValue;
                        value /= BigInteger.Pow(10, exponent - ushort.MaxValue);
                    }
                }
            }

            return(new BigDecimal(value, scale));
        }
Example #2
0
 public static BigInteger GetRuleSpaceSize(int stateCount)
 => BigInteger.Pow(stateCount, GetRuleSpaceSizePower(stateCount));
 public static BigInteger Pow(this BigInteger value, BigInteger exponent)
 {
     return(BigInteger.Pow(value, (int)exponent));
 }