LINUX.ORG.RU

возведение в дробную степень элементарными действиями

 


0

1

Ранее была тема десятичный логарифм элементарными действиями.

Для любого языка программирования доступна математическая функция возведения в произвольную степень, например в степень 3.412. Но, к сожалению, это не для всех ЯП. В языке Verilog есть возможность работать с числами с плавающей точкой (для несинтезируемых тестбенчей), не более того.

Интересует способ реализации возведения в дробную степень (обратное действие логарифмирование) используя элементарные операции с числами с плавающей точкой.

Например увеличение напряжения в 1.5 раза это 20×log(1.5) = 3,5218 дБ. Чтобы получить обратно эти 1.5 нужно 10^(3.5218÷20) = 10^0.17609 ~ 1.5

Я пока не представляю как возвести число в степень 0.1, для меня это как представить 5-мерное пространство. В интернете объясняют только через дроби, либо предлагают «калькулятор онлайн»...

Я пока не представляю как возвести число в степень 0.1, для меня это как представить 5-мерное пространство. В интернете объясняют только через дроби

Чем тебя не устраивает объяснение через дроби? Оно же простое и понятное.

Psych218 ★★★★★
()

Алгоритм для вычисления корня n-ной степени?

Zaskar
()

Я пока не представляю как возвести число в степень 0.1

Это суть радикал 10й степени. Про то как вычислять арифметическими действиями выше написал

Zaskar
()
Ответ на: комментарий от Zaskar

Про то как вычислять арифметическими действиями выше написал

Выше это где? Как вычислить корень степени n?

I-Love-Microsoft ★★★★★
() автор топика

А с наименьшим количеством арифметических дейстий будет наверное разложение в ряд (1 + x) ^ a

Zaskar
()

Не страдай фигнёй, в IEEE 754 практически тривиально считаются log2 и exp2, любое возведение в степень выражается через них.

mix_mix ★★★★★
()
Ответ на: комментарий от I-Love-Microsoft

Причем здесь вообще метод Ньютона? Ссылка же на обобщенный алгоритм для степени n

Имеет ли имя это разложение в ряд для нахождения корня, чтобы я загуглил его?

Разложение в ряд не для нахожденя корня, а для решения твоего вопроса самым быстрым путем. Ряды Тейлора смотри

Zaskar
()
Ответ на: комментарий от mix_mix

Я догадываюсь, но ТС вряд ли спрашивает для того чтобы это реализовывать. Ему просто интересно, наверное, ну или не знаю

Zaskar
()

Это называется показательная функция. Она же экспонента. Ряд для экспоненты - в гугле. Аппроксимации там же.

invy ★★★★★
()
x^{0,1} = e^{0,1*ln(x)}

Как найти логарифм тебе уже писали.

e^{t} так же находишь через ряд тейлора

zhekas
()
Ответ на: комментарий от Zaskar

ТС вряд ли спрашивает для того чтобы это реализовывать. Ему просто интересно, наверное, ну или не знаю

абсолютно не интересно ;) и да, спрашиваю чтобы реализовать сначала на C++, затем перенести на Verilog

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от invy

zhekas Точно! Можно же через это: Экспоненциальная функция может быть определена различными эквивалентными способами. Например, через ряд Тейлора. Как-то я забыл про то что экспоненту вычислить можно через ряд, слабоват я в математике... Вроде и ограничения по диапазону нету.

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от I-Love-Microsoft

Тогда то, что я говорил про корни это точно не нужно. А выше кто-то говорит что ряд тоже медленный. Надо узнать его про быстрый способ, а вообще, почему бы не посмотреть как это в glibc например

Zaskar
()
Ответ на: комментарий от Zaskar

почему бы не посмотреть как это в glibc например

Там ясно как - тупо FPU. Кстати, уже к теме не относится, но какой алгоритм работы FPU у x86 или у ARM? Как-то туго гуглится, но мне это интересно. Знаю только про https://ru.wikipedia.org/wiki/CORDIC но в FPU может что-то другое.

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от I-Love-Microsoft

Чето не очень считает, более менее точно от 0.0 до 3.0:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

double my_exp(double x);
int fact(int n);

// e^3.456 = 31,689962805

int main()
{
	printf("my_exp(3.456)= %.5f\n", my_exp(3.456));

	bool failed = false;
	for(int i = 0; i < 5000; i++)
	{
		double x = (double) (rand() % 300) / 100;
		double my_e = my_exp(x);
		double et_e = exp(x);
		double err = fabs(et_e - my_e);
		printf("%.3f: %5.3f -> %5.3f @ %.3f", x, my_e, et_e, err);
		if(err > 0.1)
		{
			printf("					!!!\n");
			failed = true;
		}
		else printf("\n");
	}

	if(failed) printf("\nFAILED\n");
	else printf("\nOK\n");

	return 0;
}

double my_exp(double x)
{
	double res = 1 + x;
	for(int i = 2; i < 10; i++)
	{
		double nx = x;
		for(int j = 1; j < i; j++) nx *= x;
		res += (double) nx / fact(i);
	}
	return res;
}

int fact(int n)
{
	if(n < 0) return 0;
	if(!n) return 1;
	return n * fact(n - 1);
}

I-Love-Microsoft ★★★★★
() автор топика

Решение задачи:

10^x = e^(x*ln10)
ln10 это константа 2.302585093, а функция my_e() у меня уже есть.

double delog20(double x)
{
	return my_exp((double) x * M_LN10 / 20);
}

Вплоть до 35 дБ мне точности хватает!

Проблема решена :)

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от I-Love-Microsoft

Точность ряда тейлора определяется следующим членом последовательности.

Тоесть чтобы точность была скажем a=1/1000, необходмо, чтобы x^{n+1}/(n+1)! < a, тогда ряд до x^n/n! будет обладать необходимой точностью.

zhekas
()

См. код вычисления экспоненты http://www.netlib.org/fdlibm/e_exp.c Как видишь, все очень просто.

1) Сначала приводим аргумент x к отрезку [0,ln(2)/2=0.34658], вычитая из x целое k ln(2). Теперь функция выглядит как exp(ln(2)*k+r) = exp(ln(2)*k) * exp(r) = 2^k * exp(r)

2) потом аппроксимируем полиномом exp(r) на этом отрезке, а результат домножаем на 2^k. Вот и все.

trycatch ★★★
()

Посчитать квадратный корень, корень из корня etc. Взять двоичное представление степени и умножить нужные.

anonymous
()
Ответ на: комментарий от I-Love-Microsoft

Там ясно как - тупо FPU.

FPU есть далеко не везде.

Deleted
()
Ответ на: комментарий от I-Love-Microsoft

res += (double) nx / fact(i);

Только в продакшн такое не пихай. Вот так правильнее:

double my_exp(double x) {
        double res = 1 + x;
        double nx  = x;
        int    n   = 1;
        for (int i = 2; i < 14; ++i) {
                nx  *= x;
                n   *= i;
                res += nx / n;
        };
        return res;
};

При итерировании дальше 14 int переполняется.

Jini ★★
()
Ответ на: комментарий от I-Love-Microsoft

несколько ответов

1) 10^(x)=8^(2^(-N)*[2^(N)*log8(10)*x]), выделяешь целую часть выражения в квадратных скобках и возводишь в нее число 8^(2^(-N)) или 8^(2^(N)), а потом 1.0 делишь на результат.

2) Выбираешь шаг точности A(близким, но не равным 1.0, можно рациональное число) 10^(x)=A^(logA(10)*x), опять берешь целую часть и возводишь в эту степень.

3) Составляешь таблицу и ищешь в ней делением пополам. Ответ подставляешь в разложение экспоненциальной функции... если 10^(F-h)<10^x<=10^F, то 10^x=e^([F*ln(10)]-{[ln(10)](F-x)}), в квадратных - константы,в фигурных - новая неизвестная, экспоненту которой можно интерполировать рядами... и первую экспоненту делишь на вторую.

anonymous
()

Как-то идея с рядом Тейлора мне не по душе. Ну, разложишь [latex](1+x)^a[/latex] по степеням x, а там проблема, что число в дробной степени плохо считается при приближении числа к нулю. В данном примере, при [latex]x=-1[/latex]. Как следствие — малый радиус сходимости ряда Тейлора (в данном примере — единица).

Не знаю исходные условия, но если есть функция sqrt, то лучше свести задачу к возведению числа в квадрат и вычислению sqrt. С большой точностью дробную степень можно представить как ряд из степеней двойки (включающий также и отрицательные степени). Переход от степеней к числу элементарен: сумму заменяем на умножение, увеличение в два раза — возведение в квадрат, уменьшение в два раза — вычисление корня (sqrt).

iVS ★★★★★
()
Последнее исправление: iVS (всего исправлений: 1)
Ответ на: комментарий от trycatch

Это, конечно, так. Просто получать «на лету» равномерные приближения сильно затратнее чем среднеквадратичные.

buddhist ★★★★★
()
Ответ на: комментарий от buddhist

В данном случае на лету ничего вычислять не нужно, функция заранее известна и не меняется. Можно хоть неделю потратить на поиск наилучшего полинома.

trycatch ★★★
()
Ответ на: комментарий от Jini

Никаких продакшенов, суть сишного кода просто алгоритм отработать. Твой вариант обязательно попробую :)

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от Jini

Попробовал твой вариант и стало потрясающе точно! В чем же секрет, в чем была ошибка моего кода? Конкретно в «res += (double) nx / fact(i);»?

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от iVS

Вот решение :)

my_exp(0.1)= 1.105
my_exp(0.5)= 1.649
my_exp(1.0)= 2.718
my_exp(2.0)= 7.389
my_exp(3.0)= 20.085
my_exp(3.5)= 33.113

delog20(0.1)= 1.012
delog20(0.5)= 1.059
delog20(5.0)= 1.778
delog20(10.0)= 3.162
delog20(25.0)= 17.783
delog20(30.0)= 31.621
function real my_exp(real x);
real res, nx;
int n;
begin
	res = 1 + x;
	nx = x;
	n = 1;
	for(int i = 2; i < 13; ++i) begin
		nx = nx * x;
		n = n * i;
		res += nx / (n * 1.0);
		//$display("x= %.3f, i= %0d, n= %0d, nx= %.3f, res= %.5f", x, i, n, nx, res);
	end
	return res;
end
endfunction

function real delog20(real x);
real res, d20;
begin
	d20 = (x * 2.30258509299404568402) / 20.0;
	res = my_exp(d20);
	return res;
end
endfunction

I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от trycatch

Использовал как раз Тейлора, видно что точность только в районе до 3.0, зато e^(x+y+z) = (e^x)*(e^y)*(e^z), а стало быть exp(x) = exp(x/4)*exp(x/4)*exp(x/4)*exp(x/4) - можно точно посчитать когда x=12.

I-Love-Microsoft ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.