/// <summary>
        /// <para>
        /// Returns the least common multiple of the absolute value of two numbers,
        /// using the formula <c>lcm(a,b) = (a / gcd(a,b)) * b</c>.
        /// </para>
        /// Special cases:
        /// <list type="bullet">
        /// <item>The invocations <c>lcm(Int64.MinValue, n)</c> and
        /// <c>lcm(n, Int64.MinValue)</c>, where <c>abs(n)</c> is a
        /// power of 2, throw an <c>ArithmeticException</c>, because the result
        /// would be 2^63, which is too large for an int value.</item>
        /// <item>The result of <c>lcm(0L, x)</c> and <c>lcm(x, 0L)</c> is
        /// <c>0L</c> for any <c>x</c>.</item>
        /// </list>
        /// </summary>
        /// <param name="a">Number.</param>
        /// <param name="b">Number.</param>
        /// <returns>the least common multiple, never negative.</returns>
        /// <exception cref="MathArithmeticException"> if the result cannot be represented
        /// as a non-negative <c>long</c> value.</exception>
        public static long lcm(long a, long b)
        {
            if (a == 0 || b == 0)
            {
                return(0);
            }
            long lcm = FastMath.abs(ArithmeticUtils.mulAndCheck(a / gcd(a, b), b));

            if (lcm == Int64.MinValue)
            {
                throw new MathArithmeticException(new LocalizedFormats("LCM_OVERFLOW_64_BITS"), a, b);
            }
            return(lcm);
        }
        /// <summary>
        /// <para>
        /// Returns the least common multiple of the absolute value of two numbers,
        /// using the formula <c>lcm(a,b) = (a / gcd(a,b)) * b</c>.
        /// </para>
        /// Special cases:
        /// <list type="bullet">
        /// <item>The invocations <c>lcm(Int32.MinValue, n)</c> and
        /// <c>lcm(n, Int32.MinValue)</c>, where <c>abs(n)</c> is a
        /// power of 2, throw an <c>ArithmeticException</c>, because the result
        /// would be 2^31, which is too large for an int value.</item>
        /// <item>The result of <c>lcm(0, x)</c> and <c>lcm(x, 0)</c> is
        /// <c>0</c> for any <c>x</c>.</item>
        /// </list>
        /// </summary>
        /// <param name="a">Number.</param>
        /// <param name="b">Number.</param>
        /// <returns>the least common multiple, never negative.</returns>
        /// <exception cref="MathArithmeticException"> if the result cannot be represented as
        /// a non-negative <c>int</c> value.</exception>
        public static int lcm(int a, int b)
        {
            if (a == 0 || b == 0)
            {
                return(0);
            }
            int lcm = FastMath.abs(ArithmeticUtils.mulAndCheck(a / gcd(a, b), b));

            if (lcm == Int32.MinValue)
            {
                throw new MathArithmeticException(new LocalizedFormats("LCM_OVERFLOW_32_BITS"), a, b);
            }
            return(lcm);
        }
        /// <summary>
        /// Returns an exact representation of the <a
        /// href="http://mathworld.wolfram.com/BinomialCoefficient.html"> Binomial
        /// Coefficient</a>, "<c>n choose k</c>", the number of
        /// <c>k</c>-element subsets that can be selected from an
        /// <c>n</c>-element set.
        /// <para>
        /// Preconditions:
        /// <list type="bullet">
        /// <item> <c>0 <= k <= n</c> (otherwise
        /// <c>MathIllegalArgumentException</c> is thrown)</item>
        /// <item> The result is small enough to fit into a <c>long</c>. The
        /// largest value of <c>n</c> for which all coefficients are
        /// <c> < Int64.MaxValue</c> is 66. If the computed value exceeds
        /// <c>Int64.MaxValue</c> an <c>ArithMeticException</c> is
        /// thrown.</item>
        /// </list></para>
        /// </summary>
        /// <param name="n">the size of the set</param>
        /// <param name="k">the size of the subsets to be counted</param>
        /// <returns><c>n choose k</c></returns>
        /// <exception cref="NotPositiveException"> if <c>n < 0</c>.</exception>
        /// <exception cref="NumberIsTooLargeException"> if <c>k > n</c>.</exception>
        /// <exception cref="MathArithmeticException"> if the result is too large to be
        /// represented by a long integer.</exception>
        public static long binomialCoefficient(int n, int k)
        {
            CombinatoricsUtils.checkBinomial(n, k);
            if ((n == k) || (k == 0))
            {
                return(1);
            }
            if ((k == 1) || (k == n - 1))
            {
                return(n);
            }
            // Use symmetry for large k
            if (k > n / 2)
            {
                return(binomialCoefficient(n, n - k));
            }

            // We use the formula
            // (n choose k) = n! / (n-k)! / k!
            // (n choose k) == ((n-k+1)*...*n) / (1*...*k)
            // which could be written
            // (n choose k) == (n-1 choose k-1) * n / k
            long result = 1;

            if (n <= 61)
            {
                // For n <= 61, the naive implementation cannot overflow.
                int i = n - k + 1;
                for (int j = 1; j <= k; j++)
                {
                    result = result * i / j;
                    i++;
                }
            }
            else if (n <= 66)
            {
                // For n > 61 but n <= 66, the result cannot overflow,
                // but we must take care not to overflow intermediate values.
                int i = n - k + 1;
                for (int j = 1; j <= k; j++)
                {
                    // We know that (result * i) is divisible by j,
                    // but (result * i) may overflow, so we split j:
                    // Filter out the gcd, d, so j/d and i/d are integer.
                    // result is divisible by (j/d) because (j/d)
                    // is relative prime to (i/d) and is a divisor of
                    // result * (i/d).
                    long d = ArithmeticUtils.gcd(i, j);
                    result = (result / (j / d)) * (i / d);
                    i++;
                }
            }
            else
            {
                // For n > 66, a result overflow might occur, so we check
                // the multiplication, taking care to not overflow
                // unnecessary.
                int i = n - k + 1;
                for (int j = 1; j <= k; j++)
                {
                    long d = ArithmeticUtils.gcd(i, j);
                    result = ArithmeticUtils.mulAndCheck(result / (j / d), i / d);
                    i++;
                }
            }
            return(result);
        }
        /// <summary>
        /// Returns the <a
        /// href="http://mathworld.wolfram.com/StirlingNumberoftheSecondKind.html">
        /// Stirling number of the second kind</a>, "<c>S(n,k)</c>", the number of
        /// ways of partitioning an <c>n</c>-element set into <c>k</c> non-empty
        /// subsets.
        /// <para>
        /// The preconditions are <c>0 <= k <= n</c> (otherwise
        /// <c>NotPositiveException</c> is thrown)
        /// </para>
        /// </summary>
        /// <param name="n">the size of the set</param>
        /// <param name="k">the number of non-empty subsets</param>
        /// <returns><c>S(n,k)</c></returns>
        /// <exception cref="NotPositiveException"> if <c>k < 0</c>.</exception>
        /// <exception cref="NumberIsTooLargeException"> if <c>k > n</c>.</exception>
        /// <exception cref="MathArithmeticException"> if some overflow happens, typically
        /// for n exceeding 25 and k between 20 and n-2 (S(n,n-1) is handled specifically
        /// and does not overflow)</exception>
        public static long stirlingS2(int n, int k)
        {
            if (k < 0)
            {
                throw new NotPositiveException <Int32>(k);
            }
            if (k > n)
            {
                throw new NumberIsTooLargeException <Int32, Int32>(k, n, true);
            }

            long[][] stirlingS2;
            lock (STIRLING_S2)
            {
                stirlingS2 = STIRLING_S2;
                if (stirlingS2 == null)
                {
                    // the cache has never been initialized, compute the first numbers
                    // by direct recurrence relation

                    // as S(26,9) = 11201516780955125625 is larger than Long.MAX_VALUE
                    // we must stop computation at row 26
                    int maxIndex = 26;
                    stirlingS2    = new long[maxIndex][];
                    stirlingS2[0] = new long[] { 1L };
                    for (int i = 1; i < stirlingS2.Length; ++i)
                    {
                        stirlingS2[i]    = new long[i + 1];
                        stirlingS2[i][0] = 0;
                        stirlingS2[i][1] = 1;
                        stirlingS2[i][i] = 1;
                        for (int j = 2; j < i; ++j)
                        {
                            STIRLING_S2[i][j] = j * stirlingS2[i - 1][j] + stirlingS2[i - 1][j - 1];
                        }
                    }

                    // atomically save the cache, thread-safe
                    STIRLING_S2 = (long[][])stirlingS2.Clone();
                }
            }

            if (n < stirlingS2.Length)
            {
                // the number is in the small cache
                return(stirlingS2[n][k]);
            }
            else
            {
                // use explicit formula to compute the number without caching it
                if (k == 0)
                {
                    return(0);
                }
                else if (k == 1 || k == n)
                {
                    return(1);
                }
                else if (k == 2)
                {
                    return((1L << (n - 1)) - 1L);
                }
                else if (k == n - 1)
                {
                    return(binomialCoefficient(n, 2));
                }
                else
                {
                    // definition formula: note that this may trigger some overflow
                    long sum  = 0;
                    long sign = ((k & 0x1) == 0) ? 1 : -1;
                    for (int j = 1; j <= k; ++j)
                    {
                        sign = -sign;
                        sum += sign * binomialCoefficient(k, j) * ArithmeticUtils.pow(j, n);
                        if (sum < 0)
                        {
                            // there was an overflow somewhere
                            throw new MathArithmeticException(new LocalizedFormats("ARGUMENT_OUTSIDE_DOMAIN"), n, 0, stirlingS2.Length - 1);
                        }
                    }
                    return(sum / factorial(k));
                }
            }
        }