Как возвести дробное число в дробную степень? J2ME

В J2ME нет такого метода, как Math.pow(double,double). Есть ли какая-нибудь любая формула, для возведения в дробную степень? По крайней мере для MIDP-2.0.

И да, в J2ME также нет Math.log(double) и Math.exp(double), поэтому другие вопросы и ответы, которые были заданы до меня не помогли мне...


Ответы (2 шт):

Автор решения: Nowhere Man

Для решения данной задачи можно использовать приближенные вычисления, приведенные в статье 2007 года Optimized pow() approximation for Java, C / C++, and C#:

  • Первоначальный вариант
public static double pow(final double a, final double b) {
    final int x = (int) (Double.doubleToLongBits(a) >> 32);
    final int y = (int) (b * (x - 1072632447) + 1072632447);
    return Double.longBitsToDouble(((long) y) << 32);
}
  • Улучшенный вариант (2011 г.)
public static double pow(final double a, final double b) {
    final long tmp = Double.doubleToLongBits(a);
    final long tmp2 = (long)(b * (tmp - 4606921280493453312L)) + 4606921280493453312L;
    return Double.longBitsToDouble(tmp2);
}

Другие примеры реализации вычисления функции pow приведены в ещё одной статье 2007 года, доступной сейчас только в "машине времени" / вебархиве: WebArchive :: Creating a Java ME Math.pow() Method by Lawrence Fulton, Daniel Williams.

В ней рассматриваются следующие варианты:

  • простое целочисленное решение с использованием обычного цикла и умножений.
  • решение, использующее алгоритм геометрического распада (geometric decay)
  • решение, использующее функцию квадратного корня Math.sqrt
  • приближенные вычисления натурального логарифма и экспоненты при помощи ряда Тейлора

Ещё один источник, статья на хабре 2021 г.: Ускоряем pow

→ Ссылка
Автор решения: Stanislav Volodarskiy

ab. Если b < 0, выполним преобразование ab = (1/a)-b. В дальнейшем считаем что b положительное.

Разложим b в сумму степеней двоек: b = ∑2pi, где pi - целые. Тогда ab = a∑2pi = ∏a2pi.

Если pi > 0, то a2pi = (a2)2pi-1 = ((a2)2)2pi-2 = ... = ((...((a2)2)...)2)2. В последнем выражении возведение в квадрат повторяется pi раз.

Если pi < 0, то a2pi = (√a)2pi+1 = (√√a)2pi+2 = ... = √√...√√a. В последнем выражении корень извлекается -pi раз.

Если pi = 0, то a2pi = a.

Если разложить b по степеням двойки, то для вычисления ab нужны только умножение и извлечение корня квадратного. И то и другое есть в J2ME. Разлагать b по степеням не нужно, формат double уже хранит вещественное число в виде такого разложения, а Java предоставляет средства для доступа к разложению.

Этот способ довольно точный (теоретически он точный, мешают только округления при умножениях и извлечении корней), заметная относительная погрешность появляется для действительно больших степеней. Например если |b| < 109, погрешность не превышает 10-9.

    public static double pow(double a, double b) {
        if (b < 0) {
            return pow_impl(1 / a, -b);
        }
        if (b == 0) {
            return 1;
        }
        return pow_impl(a, b);
    }

    private static double pow_impl(double a, double b) {
        long bits = Double.doubleToRawLongBits(b);
        int exponent = (int)((bits & 0x7ff0000000000000L) >> 52) - 1023;
        long mantissa = bits & 0x000fffffffffffffL | 0x0010000000000000L;

        int max_j = 52;
        int min_j = Long.numberOfTrailingZeros(mantissa);
        int j0 = 52 - exponent;

        double pow = 1;
        if (min_j <= j0 && j0 <= max_j) {
            if ((mantissa & (1L << j0)) != 0) {
                pow = a;
            }
        }
        {
            double f = a;
            int first_j = Math.max(j0 + 1, min_j);
            for (int j = j0 + 1; j < first_j; ++j) {
                f *= f;
            }
            for (int j = first_j; j <= max_j; ++j) {
                f *= f;
                if ((mantissa & (1L << j)) != 0) {
                    pow *= f;
                }
            }
        }

        {
            double f = a;
            int first_j = Math.min(j0 - 1, max_j);
            for (int j = j0 - 1; j > first_j; --j) {
                f = Math.sqrt(f);
            }
            for (int j = first_j; j >= min_j; --j) {
                f = Math.sqrt(f);
                if ((mantissa & (1L << j)) != 0) {
                    pow *= f;
                }
            }
        }
        return pow;
    }

P.S. Если существует такой относительно простой и точный способ вычислить степень, то почему его нет в J2ME? Потому что Java требует от pow высокой точности:

The computed result must be within 1 ulp of the exact result. Results must be semi-monotonic.

Это означает что точность pow во всём диапазоне аргументов должна быть лишь в два раза хуже чем точность представления чисел самим форматом double. Если процессор не поддерживает аппаратное вычисление pow, то эмуляция с требуемой точностью будет очень медленной. Требование монотонности делает задачу ещё сложнее. А отступать от требований стандарта нельзя, Java должна работать везде одинаково (почти одинаково), это одна из целей создания языка. Какой же выход? Отрезать часть функций из спецификации. В итоге у нас интересная задача и головная боль для разработчиков.

→ Ссылка