Как возвести дробное число в дробную степень? J2ME
В J2ME нет такого метода, как Math.pow(double,double)
.
Есть ли какая-нибудь любая формула, для возведения в дробную степень?
По крайней мере для MIDP-2.0.
И да, в J2ME также нет Math.log(double)
и Math.exp(double)
, поэтому другие вопросы и ответы, которые были заданы до меня не помогли мне...
Ответы (2 шт):
Для решения данной задачи можно использовать приближенные вычисления, приведенные в статье 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
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 должна работать везде одинаково (почти одинаково), это одна из целей создания языка. Какой же выход? Отрезать часть функций из спецификации. В итоге у нас интересная задача и головная боль для разработчиков.