1

I've noticed that calculators and graphing programs like Desmos or Geogebra or Google (google search x^(1/3)) must have modified version the Math.pow() function that allows them to plot some negative bases and fractional exponents that would otherwise be undefined using the regular Math.pow() function.

I'm trying to recreate this modified pow function so I can plot the missing sections of graphs, for example $x^{\frac{1}{3}}$ where $x<0$

My Attempt

I'm not aware what this modified pow function is called in computer science or math literature, so I can't look up references that would help make a robust and optimized version of it. Instead, I've attempted to make my own version with the help of the Fraction class from Apache math3 library to determine some of the conditional statements like when the numerator and denominator is even or odd for fractional exponents.

My version has a few issues that I'll outline and might be missing some extra conditions that I haven't considered which could lead to errors.

    /* Main Method */
    public static void main(String[] args) {
        double xmin = -3;
        double xmax = 3;
        double epsilon = 0.011;

        /* print out x,y coordinates for plot */
        for (double x = xmin; x <= xmax; x += epsilon){
            System.out.println(x+","+f(x));
        }
    }

    /* Modified Power Function*/
    private static double pow2(double base, double exponent){
        boolean negativeBase = base < 0;

        /* exponent is an integer and base non-negative */
        if (exponent == ((int) exponent) && !negativeBase){
            /* use regular pow function */
            return Math.pow(base, exponent);
        }

        Fraction fraction;
        try {
            fraction = new Fraction(exponent, 1000); /* maxDenominator of 1000 for speed */
        } catch (FractionConversionException e){
            return Double.NaN;
        }

        /* updated: irrational exponent */
        if (exponent != fraction.doubleValue()){
            /* handles irrational exponents like π which cannot be reduced to fractions.
             * depends heavily on the maxDenominator value set above.
             * With a maxDenominator of 1000, fractions like 1/33333 who's denominator has greater then 4 digits
             * will be considered as irrational. To avoid this, increase maxDenominator to 10000 but will slow down performance.
             * That's the trade off with this part of the algorithm.
             * Also this condition helps clear up a lot the mess on a plot.
             * If the plot is centered at exactly origin (0,0) the messy artifacts may appear, but by offsetting
             * the view of the plot slightly from the origin will make it disappear
             * or maybe it has more to do with the stepsize epsilon (0.01 (clean number) vs 0.011 (irregular number))
             * */
            return Math.pow(base, exponent);
        }

        if (fraction.getDenominator() % 2 == 0){
            /* if even denominator */
            if (negativeBase){
                return Double.NaN;
            }
        } else {
            /* if odd denominator, allows for negative bases */

            if (negativeBase){
                if (fraction.getNumerator() % 2 == 0){
                    /* if even numerator
                     * (-base)^(2/3) is the same as ((-base)^2)^(1/3)
                     * any negative base squared is positive */
                    return Math.pow(-base, exponent);
                }

                /* return negative answer, make base and exponent positive */
                return -Math.pow(-base, exponent);
            }
        }

        return Math.pow(base, exponent);
    }

    /* Math function */
    private static double f(double x){
        /* example f(x) = x^(1/x) */
        return pow2(x, (double) 1/x);
    }

Issue #1

For both issues, I'll be using the math function $f(x) = x^{\frac{1}{x}}$ as an example for a plot that demonstrates both issues - the first being the FractionConversionException that is caused by having a large value for the exponent. This error will occur if the value of epsilon in my code is changed to 0.1, but seems to avoid the error when the stepsize epsilon is 0.11. I'm not sure how to resolve it properly, but looking within the Fraction class where it throws the FractionConversionException, it uses a conditional statement that I could copy over to my pow2() function and get it to return NaN with the code below. But I'm not sure if that the correct thing to do!

        long overflow = Integer.MAX_VALUE;

        if (FastMath.abs(exponent) > overflow) {
            return Double.NaN;
        }

EDIT: Adding a try/catch statement around the instantiation of the Fraction class and returning NaN in the catch clause seems to be a good workaround for now. Instead of the above code.

Issue #2

Plotting the math function $f(x) = x^{\frac{1}{x}}$ produces a messy section on the left where $x<0$, see image below

x^(1/x) plot

as opposed to what it should look like

https://www.google.com/search?q=x^(1%2Fx)

I don't how to get rid of this mess, so that where $x<0$ it should be undefined (NaN), while allowing the pow2() function to still plot functions like $x^{\frac{1}{3}}$,$x^{\frac{2}{3}}$,$x^{x}$, etc...

I'm also not sure what to set the maxDenominator when instantiating the Fraction object for good performance while not affecting the results of the plot. Maybe there's a faster decimal to fraction conversion algorithm out there, although I'd imagine Apache math3 is probably very optimized.

Edit: Issue 3 I forgot to consider irrational exponents because I was too consumed with neat fractions. My algorithm fails for irrational exponents like π and plots two versions of the graph together. Because of the way the Fraction class rounds the exponent, some of the denominators are considered even and odd. Maybe having a condition that if the irrational exponent doesn't equal the fraction to instead return NaN. Just quickly tested this condition, had to add a negativeBase condition which flips the graph the right way. Need to do further testing, but might be an idea.

Edit2: After testing, it should actually return the regular pow() function instead of NaN to handle to conditions for irrational exponents (see updated code for the modified power function) and also this approach surprisingly manages to get rid of most of the mess highlighted in Issue #2 as I believe there are more irrational numbers in an interval than rational number which are being discounted by the algorithm making it less dense and harder to connect two points into a line to appear on the plot.


        if (exponent != fraction.doubleValue() && negativeBase){
            return Double.NaN;
        }

Extra Question Is it accurate to represent/plot this data of a function like most modern graphing programs (mentioned above) seem to do or is it really misleading considering that the regular power function considers this extra data for negative bases or exponents as undefined? And what these regions or parts of the plot called in mathematics (it's technical term)?

Also open to any other approach or algorithm

C9C
  • 319
  • 3
  • 14
  • I believe the standard way of doing this is 1) take the natural log of the base `b`, multiply the result by the exponent `z`, then raise *e* to the power of that result. Read up: https://en.wikipedia.org/wiki/Exponentiation#Powers_via_logarithms – markspace Jun 15 '19 at 02:07
  • That just seems like an alternative way of doing the regular power function and that you can't use the negative base for the natural log. So I would have to modify that version as well. – C9C Jun 15 '19 at 04:50
  • It is the normal power function. E.g. x^(1/3) is the same as the third root of x. And the third root of a negative number is defined as the negative result of the third root of the absolute of the number. – dan1st Jun 15 '19 at 05:38
  • I know the negative of third root is defined in maths, but it doesn't seem to be in Java e.g. Math.pow(-3,(double)1/3) == NaN. Using the formula mentioned above via logarithms, Math.exp(Math.log(-3)*((double)1/3)) == NaN – C9C Jun 15 '19 at 05:57
  • take a look at [Power by squaring for negative exponents](https://stackoverflow.com/a/30962495/2521214) – Spektre Jun 15 '19 at 08:59
  • Beware that if the *base* is negative then the result will generally be a **complex** number (and will not be unique). Same applies to logarithms. – meowgoesthedog Jun 15 '19 at 15:36
  • @Spektre Oh this is kind of algorithm I was worried about, it looks so technically complicated with bit-wise operators. That's why I stuck with the simple fractions class. I'll attempt to implement it as best I can in Java and test it out. Thanks for the suggestion. – C9C Jun 15 '19 at 19:12
  • @C9C its usually just a binary search so its also doable without binary operations on floats but that is magnitude slower than using integer math and bit operations (which can be done on floats too) ... – Spektre Jun 15 '19 at 20:49
  • @Spektre After looking into your optimised algorithm, it doesn't seem like Java supports inline ASM that's required for your mult and div methods or is there an alternative way in Java. Secondly, the bits() method, does it convert the input x into byte data type or binary 1's and 0's representation. – C9C Jun 17 '19 at 23:00
  • @C9C see [Building a logarithm function in C without using float type](https://stackoverflow.com/a/42108287/2521214) at the end is the same `mul` implemented in C++ without `asm` for the same reasons. You can use also binary multiplication and division (the same as you compute on paper but in binary that is easy) there are also another options like [How to divide large numbers and what algorithm to use](https://stackoverflow.com/a/19382462/2521214) the same goes for multiplication there are a lot of approaches see [Fast bignum square computation](https://stackoverflow.com/q/18465326/2521214) – Spektre Jun 18 '19 at 07:13

0 Answers0