Solución:
Empezar con:
$$ 2 ^ 3.1 = 2 ^ 3 2 ^ 0.1 = 2 ^ 3 e ^ 0.1 log 2 $$
Ahora use una expansión de Taylor, de modo que lo anterior sea aproximadamente
$$ 2 ^ 3 izquierda [1+0.1 log2 + frac12! (0.1 log2)^2 + frac13! (0.1 log2)^3+cdots + frac1n! (0.1 log2)^nright ] $$
donde $ n $ depende de la tolerancia que requiera. En este caso, si esta tolerancia de error es $ epsilon $, entonces queremos
$$ frac 1 (n + 1)! (0.1 log 2) ^ n + 1 lt epsilon $$
Por ejemplo, si $ epsilon = 5 cdot 10 ^ – 7 $, luego $ n = 4 $.
Para responder una pregunta que tenía en el pasado: P La idea básica es que en realidad se pueden calcular ambos directamente.
$$ 2 ^ 3.1 = e ^ 2 log left (3.1 right) $$
Entonces, realmente el procedimiento es simple:
- Tome el logaritmo natural de 3,1
- Multiplicar por la base (2)
- Utilice el resultado como argumento para $ exp (x) $ o $ e ^ x $
Entonces, este problema se reduce realmente a, ¿cómo calculan las calculadoras logaritmos y exponenciales? La respuesta a eso no es tan difícil. No estoy dando los métodos más rápidos de ninguna manera, hay algunas series que convergen a estos valores mucho más rápido que las que he sugerido, pero esto debería encaminar a cualquiera que vea esto en el futuro con bastante rapidez :).
Calcular el registro
Para calcular el logaritmo sería suficiente un método numérico como la regla trapezoidal o la regla de Simpson; porque:
$$ log (x) = int_1 ^ x 1 over t dt $$
Calcular el exponencial
Aquí podríamos usar la definición de límite directo:
$$ exp (x) = lim_ n rightarrow infty left (1 + x over n right) ^ n $$
Donde n es solo un entero grande (por lo que simplemente multiplica directamente), o nuevamente, podrías aproximar $ e ^ x $ con una serie de Taylor, que debería converger a la respuesta real un poco más rápido, ya que:
$$ exp (x) = lim_ n rightarrow infty left (1 + x + x ^ 2 over 2! + x ^ 3 over 3! + … + x ^ n over n! right) $$
Sin embargo, la mayoría de las calculadoras almacenan algunos de estos valores en una LUT (tabla de consulta), ya que serán (al menos parcialmente) precalculados con más precisión de la que probablemente necesite. Esto permite que las computadoras modernas encuentren el exponencial y el logaritmo de una función muy rápidamente. [although X86 – which is the instruction set used by most common computers today actually calculates $2^x$ and $log_2 (x)$, because its easy to calculate them in binary (base 2)].
Solo quería calcular directamente el valor del número. $ 2 ^ 3.1 $mientras me preguntaba cómo lo haría una computadora
(énfasis mío)
Lo que sigue es posiblemente una de las respuestas más detalladas que he escrito en esta red. Creo que esto responde completamente a su pregunta sobre “cómo lo haría una computadora”, por lo que merece una lectura.
Esta respuesta se divide en cuatro secciones principales, tituladas:
- Descubriendo el dominio del problema – donde muestro cómo resolver el problema a mano
- Llegar a $ 8 sqrt[10]2 $ programáticamente – donde muestro cómo hacer que una computadora resuelva el problema
- Implementando root (exp, x) – describiendo cómo implementar enésimas aproximaciones de raíz
- Una implementación real – una implementación completa de C que culmina en el cálculo de $ 2 ^ 3.1 $, entre otras cosas.
Esto no es StackOverflow, así que intentaré respetarlo y evitar entrar en demasiados detalles técnicos. Dicho esto, cualquier código de ejemplo que no sea pseudocódigo que incluya estará en C, ya que C tiene un nivel de abstracción relativamente bajo y, por lo tanto, un algoritmo implementado en C es más hormigóny, por lo tanto, se puede migrar más fácilmente a otros idiomas.
Descubriendo el dominio del problema
Dicho esto, la forma en que abordaría esto es representando primero el exponente como una fracción, factorizando y simplificando eso, y resolviendo el radical simplificado.
$ 2 ^ 3.1 = \ 2 ^ frac 31 10 = \ sqrt[10]2 ^ 31 = \ sqrt[10] 2 ^ 10 * 2 ^ 10 * 2 ^ 10 * 2 ^ 1 = \ 2 * 2 * 2 * sqrt[10] 2 = \ 8 sqrt[10] 2 $
Ahora tenemos algo que se puede representar en un programa:
8 * root(10, 2)
Llegar a $ 8 sqrt[10]2 $ programáticamente
Por supuesto, pasando de pow(2, 3.1)
para 8 * root(10, 2)
con una máquina que solo entiende cómo realizar sumas y multiplicaciones no es exactamente trivial, por lo que probablemente querrá separar los componentes entero y decimal; es decir, lo que hiciste.
#define GET_INT(n)
( (double)((int)(n)) )
#define GET_DEC(n)
( (n) - GET_INT(n) )
#define IS_INT(n)
( GET_INT(n) == (n) )
Por lo tanto, $ 2 ^ 3.1 = ~ … $
pow(a, b):
IS_INT(b):
pow(a, b)
otherwise:
bi = GET_INT(b)
bf = GET_DEC(b)
pow(a, bi) * root( get_denom(bf), pow(a, get_numer(bf)) )
pow(2, 3.0):
IS_INT(3.0)
8
pow(2, 3.2):
otherwise
bi = 3.0
bf = 0.2
pow(2, 3.0) * root(10, pow(2, 2))
dónde get_numer
y get_denom
son funciones que toman, por ejemplo, 0.2
y volver 2
y 10
, respectivamente. No estoy seguro de cómo implementar estas dos funciones, pero sé que es factible. Por ejemplo, puedes multiplicar bf
por 10 hasta IS_INT(bf)
es true, y luego regresa bf
para el numerador y $ 10 ^ el ~ número ~ de ~ veces ~ que ~ multiplicaste ~ bf ~ por ~ 10 $ para el denominador, pero ¿Esto cubre satisfactoriamente el dominio del problema y funciona sin errores inesperados relacionados con el punto flotante, errores de redondeo o desbordamiento / subdesbordamiento?? No puedo contestar eso. Todo lo que puedo decir es que el enfoque debería funcionar En teoria para un rango decente de valores. La implementación de nuestro enfoque ingenuo se ve así:
double *dec_to_frac(double x)
double magnitude = 1;
static double ret[2];
while(IS_INT(x) == 0)
x *= 10;
magnitude *= 10;
ret[0] = x;
ret[1] = magnitude;
return ret;
double get_numer(double x)
double *frac = dec_to_frac(x);
return frac[0];
double get_denom(double x)
double *frac = dec_to_frac(x);
return frac[1];
Dicho esto, ahora hemos establecido un algoritmo para calcular $ a ^ b $ para valores compatibles con flotador> 0.
Así que ahora, para una aportación de $ 2 ^ 3.1 $, tienes la siguiente expresión C:
pow(a, bi) * root(get_denom(bf), pow(a, get_numer(bf)))
dónde a = 2.0, bf = 0.1, bi = 3.0
Tenemos todo lo que necesitamos para calcular una buena aproximación, excepto una función, root
, que ahora voy a repasar.
Implementando root (exp, x)
Primero, necesitamos establecer en base a un ejemplo, por lo que comenzaremos con raíces cúbicas.
En pocas palabras, el objetivo es convertir el problema en una ecuación, convertir la ecuación en una función $ f (n) $, encuentra la derivada de esa función $ f ‘(n) $y luego calcular repetidamente $ n – frac f (n) f ‘(n) $ dónde $ n $ es el valor inicial o “conjetura”. Esto le dará una aproximación.
$ sqrt[3]x = n ~ donde ~ n ^ 3 = x \ n ^ 3 = x \ n ^ 3 – x = 0 \ f (n) = n ^ 3 – x \ f ‘(n) = 3n ^ 2 \ g (n) = n – frac f (n) f ‘(n) \ sqrt[3]x approx g ^ P (Q) ~ donde ~ P ~ es ~ la ~ precisión ~ deseada ~ y ~ Q ~ es ~ la ~ aproximación ~ inicial $
Generalizando esto, obtenemos
$ sqrt[I]x = n ~ donde ~ n ^ I = x \ n ^ I = x \ n ^ I – x = 0 \ f (n) = n ^ I – x \ f ‘(n) = En ^ Yo – 1 \ g (n) = n – frac f (n) f ‘(n) \ sqrt[I]x aprox g ^ P (Q) $
Lo que creo que se expresa correctamente como …
$ sqrt[I]x approx g ^ P (Q) ~ donde … \ g (n) = n – frac f (n) f ‘(n) \ f (n) = n ^ I – x \ f ‘(n) = En ^ I – 1 \ $
Ahora podemos implementar
#define PRECISION 10
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
double root(int exp, double x)
double guess = MAX(1.0, x / exp);
for(int i = 0; i < PRECISION; ++i)
guess -= (pow(guess, (double)exp) - x) / ( exp * pow(guess, (double)(exp - 1)) );
return guess;
Poniendo todo eso junto en un especializado pow(double, double)
función, con una entrada de (2.0, 3.1)
, debería obtener una salida de aproximadamente 8.574187700290345
.
Una implementación real
Lo que sigue es mi implementación, que no garantizo que sea robusta. También agregué algunos detalles adicionales para tener en cuenta algunas idiosincrasias complicadas con respecto a cómo la computadora representa los flotadores.
#include
#include
#define PRECISION 20
#define MAX(a, b)
( (a) > (b) ? (a) : (b) )
#define MIN(a, b)
( (a) < (b) ? (a) : (b) )
#define GET_INT(n)
( (double)((int)(n)) )
#define GET_DEC(n)
( (n) - GET_INT(n) )
#define IS_INT(n)
( MAX((n), GET_INT(n)) - MIN((n), GET_INT(n)) <= FLT_EPSILON )
int *dec_to_frac(double);
int get_numer(double);
int get_denom(double);
double fpow(double, double);
double ipow(double, int);
double root(int exp, double x);
int *
dec_to_frac(double x)
int magnitude = 1;
static int ret[2];
while(IS_INT(x) == 0)
x *= 10;
magnitude *= 10;
ret[0] = (int)x;
ret[1] = magnitude;
return ret;
int
get_numer(double x)
return (dec_to_frac(x))[0];
int
get_denom(double x)
return (dec_to_frac(x))[1];
double
fpow(double a, double b)
double bi;
double bf;
if(IS_INT(b))
return ipow(a, (int)b);
bi = GET_INT(b);
bf = GET_DEC(b);
return ipow(a, (int)bi) * root( get_denom(bf), ipow(a, get_numer(bf)) );
double
ipow(double a, int b)
double total = 1.0;
while(b--)
total *= a;
return total;
double
root(int exp, double x)
double guess = MAX(1.0, x / exp);
for(int i = 0; i < PRECISION; i++)
guess -= (ipow(guess, exp) - x) / ( exp * ipow(guess, exp - 1) );
return guess;
int
main(int argc, char **argv)
printf("pow(2, 3) = %.1fn", fpow(2.0, 3.0));
printf("pow(2, 3.1) = %.15fn", fpow(2.0, 3.1));
printf("root(2, 2) = %.15lfn", root(2, 2.0));
printf("root(3, 3) = %.15lfn", root(3, 3.0));
printf("root(2, 9) = %.15lfn", root(2, 9.0));
printf("root(4, 81) = %.15lfn", root(4, 81.0));
return 0;
La ejecución de este programa genera la siguiente salida:
pow(2, 3) = 8.0
pow(2, 3.1) = 8.574187700290345
root(2, 2) = 1.414213562373095
root(3, 3) = 1.442249570307408
root(2, 9) = 3.000000000000000
root(4, 81) = 3.000000000000000