C# Math.Pow() is broken

2019-01-26 14:34发布

问题:

And no, this does not (to my understanding) involve integer division or floating-point rounding issues.

My exact code is:

    static void Main(string[] args)
    {
        double power = (double)1.0 / (double)7.0;
        double expBase = -128.0;
        System.Console.WriteLine("sanity check: expected: -128 ^ 0.142857142857143 = -2.    actual: " + expBase + " ^ " + power + " = " + Math.Pow(expBase, power));
        System.Console.ReadLine();
    }

The output is:

sanity check: expected: -128 ^ 0.142857142857143 = -2. actual: -128 ^ 0.14285 7142857143 = NaN

The Target Framework for this code is (according to solution properties) .NET Framework 4.0 Client Profile.

Strangely I haven't found any mention of this anywhere on the Web. Am I taking crazy pills here!?

回答1:

Seems to be exactly as specified; from the Math.Pow() remarks section on Pow(x,y);

Parameters
x < 0 but not NegativeInfinity; y is not an integer, NegativeInfinity, or PositiveInfinity.

Result
NaN



回答2:

Joachim's answer explains that pow is behaving according to its specification.

Why is pow( ) specified that way? Because 1.0/7.0 is not equal to 1/7. You are asking for the 0.14285714285714285 power of -128.0, and there is no real number with that property, so the result is correctly NaN. For all odd n != 1, 1.0/(double)n is not exactly representable, so you can't compute the nth root of x by using pow(x, 1.0/(double)n). Therefore pow(x, y) is specified to return NaN for negative x and non-integer y -- there is no appropriate real result for any of those cases.



回答3:

The problem here is the mathematical definition of "seventh root" is a multivalued function. While it is true that

(-2)7 = -128

this does not mean that -2 is the only answer to (-128)1/7. In the complex plane, the seventh-root function is multivalued and has many possible answers (just as the square root of 4 can be considered to be either +2 or -2, but +2 is the usual answer).

In order to simplify the mathematical handling of such expressions, the principal value is chosen by convention for the function in question so that the function becomes single-valued. In the case of seventh-root, the principal value is that given by Wolfram Alpha for (-128)1/7.

The Math.Pow() function in C# attempts to return the principal value for the pow function. When the principal value of the result would be a complex number, it returns NaN.



回答4:

A fractional power of a negative real number is a complex number (see Math Forum for a detailed explanation).



回答5:

I fixed Math.Pow().

It now has a larger accepted domain (i.e. for Parameters: x < 0 but not NegativeInfinity; y is a fraction with a numerator of 1 and an odd denominator), and returns a real number result for the new domain area.

In other words, (-128)^(1/7) returns -2.

Note: due to double-float precision limitations, it will work for most, but not all, fractional exponents.

Below is the code for the wrapper for Math.Pow() I wrote.

public class MathUtil
{
    /// <summary>
    /// Wrapper for Math.Pow()
    /// Can handle cases like (-8)^(1/3) or  (-1/64)^(1/3)
    /// </summary>
    public static double Pow(double expBase, double power)
    {
        bool sign = (expBase < 0);
        if (sign && HasEvenDenominator(power)) 
            return double.NaN;  //sqrt(-1) = i
        else
        {
            if (sign && HasOddDenominator(power))
                return -1 * Math.Pow(Math.Abs(expBase), power);
            else
                return Math.Pow(expBase, power);
        }
    }

    private static bool HasEvenDenominator(double input)
    {
        if(input == 0)
            return false;
        else if (input % 1 == 0)
            return false;

        double inverse = 1 / input;
        if (inverse % 2 < double.Epsilon)
            return true;
        else
            return false;
    }

    private static bool HasOddDenominator(double input)
    {
        if (input == 0)
            return false;
        else if (input % 1 == 0)
            return false;

        double inverse = 1 / input;
        if ((inverse + 1) % 2 < double.Epsilon)
            return true;
        else
            return false;
    }
}


回答6:

For 1.0/3 != 1/3 ,I use Rational that can Accurately represent 1/3 in Microsoft.SolverFoundation.Common .See:https://msdn.microsoft.com/en-us/library/microsoft.solverfoundation.common.rational%28v=vs.93%29.aspx?f=255&MSPPError=-2147217396

And I can catch the odd root link 1/3 for I can get the Denominator.

If I get a root is ax that I use the code get the Numerator and Denominator.

            var at = (double)ax.Numerator;
            var down = (double)ax.Denominator;

Rational can make the 2/6=1/3.

But the Rational.Pow cant calculate powerBase isnt positive.

I find the powerBase isnt positive and the Denominator is even and Numerator is odd.

             if (at % 2 == 1 && down % 2 == 0)
            {
                return Double.NaN;
            }

If the Denominator is odd ,I use x = x * -1

            if (at % 2 == 1 && down % 2 == 1)
            {
                x = Math.Pow(x, (int)at);
                x = x * -1;
                return -1 * Math.Pow(x, 1.0 / (int)down);
            }

If the Numerator is even ,the pow of Numerator make powerBase is positive.

Like pow(x,2/3), if x isnt positive ,it will be positive when use pow(x,2)

            x = Math.Pow(x, (int)at);
            return Math.Pow(x, 1.0 / (int)down);

The code you can use

       if (x < 0)
        {                
            var at = (double)ax.Numerator;
            var down = (double)ax.Denominator;

            if (at % 2 == 1 && down % 2 == 0)
            {
                return Double.NaN;
            }
            if (at % 2 == 1 && down % 2 == 1)
            {
                x = Math.Pow(x, (int)at);
                x = x * -1;
                return -1 * Math.Pow(x, 1.0 / (int)down);
            }
            x = Math.Pow(x, (int)at);
            return Math.Pow(x, 1.0 / (int)down);
        }
        else
        {
            return Math.Pow(x, a);
        }