¿Cómo llevar el BigInteger al pow Double en C #?

Intenté usar el método BigInteger.Pow para calcular algo como 10 ^ 12345.987654321 pero este método solo acepta el número entero como exponente de esta manera:

BigInteger.Pow (BigInteger x, int y)

Entonces, ¿cómo puedo usar el número doble como exponente en el método anterior?

No existe una compatibilidad con números grandes de precisión arbitraria en C #, por lo que no se puede hacer directamente. Existen algunas alternativas (como buscar una biblioteca de terceros), o puede intentar algo como el código a continuación, si la base es lo suficientemente pequeña, como en su caso.

 public class StackOverflow_11179289 { public static void Test() { int @base = 10; double exp = 12345.123; int intExp = (int)Math.Floor(exp); double fracExp = exp - intExp; BigInteger temp = BigInteger.Pow(@base, intExp); double temp2 = Math.Pow(@base, fracExp); int fractionBitsForDouble = 52; for (int i = 0; i < fractionBitsForDouble; i++) { temp = BigInteger.Divide(temp, 2); temp2 *= 2; } BigInteger result = BigInteger.Multiply(temp, (BigInteger)temp2); Console.WriteLine(result); } } 

La idea es usar números enteros grandes para calcular la potencia de la parte entera del exponente, luego usar matemáticas dobles (punto flotante de 64 bits) para calcular la potencia de la parte de la fracción. Entonces, usando el hecho de que

 a ^ (int + frac) = a ^ int * a ^ frac 

Podemos combinar los dos valores en un solo entero grande. Pero simplemente convertir el valor doble en un BigInteger perdería mucha de su precisión, por lo que primero "cambiamos" la precisión al bigInteger (usando el bucle de arriba, y el hecho de que el tipo double usa 52 bits para la precisión), luego Multiplicando el resultado.

Tenga en cuenta que el resultado es una aproximación; si desea un número más preciso, necesitará una biblioteca que realice cálculos matemáticos de punto flotante de precisión arbitraria.

Actualización : Si la base / exponente es lo suficientemente pequeño como para que la potencia esté en el rango de double , simplemente podemos hacer lo que Sebastian Piu sugirió ( new BigInteger(Math.Pow((double)@base, exp)) )

Me gusta la respuesta de carlosfigueira, pero, por supuesto, el resultado de su método solo puede ser correcto en los primeros 15-17 dígitos (los más significativos), ya que eventualmente se usa un System.Double como multiplicador.

Es interesante observar que existe un método BigInteger.Log que realiza la operación “inversa”. Por lo tanto, si desea calcular Pow(7, 123456.78) , en teoría podría buscar en todos los números BigInteger x para encontrar un número tal que BigInteger.Log(x, 7) sea ​​igual a 123456.78 o más cercano a 123456.78 que cualquier otra x de tipo BigInteger .

Por supuesto, la función de logaritmo está aumentando, por lo que su búsqueda puede usar algún tipo de “búsqueda binaria” (búsqueda por bisección). Nuestra respuesta se encuentra entre Pow(7, 123456) y Pow(7, 123457) que se pueden calcular con exactitud.

Sáltate el rest si quieres.

Ahora, ¿cómo podemos predecir de antemano si hay más de un entero cuyo logaritmo es 123456.78 , hasta la precisión de System.Double , o si de hecho no hay un entero cuyo logaritmo scope ese Double específico (el resultado preciso de un ideal)? Pow función de Pow es un número irracional? En nuestro ejemplo, habrá muchos enteros que darán el mismo Double 123456.78 porque el factor m = Pow(7, epsilon) (donde epsilon es el número positivo más pequeño, de manera que 123456.78 + epilon tiene una representación como un Double diferente de la representación de 123456.78 sí) es lo suficientemente grande como para que haya muchos enteros entre la respuesta verdadera y la respuesta verdadera multiplicados por m .

Recuerde del cálculo que la derivada de la función matemática x → Pow(7, x) es x → Log(7)*Pow(7, x) , por lo que la pendiente de la gráfica de la función exponencial en cuestión será Log(7)*Pow(7, 123456.78) . Este número multiplicado por el epsilon anterior es todavía mucho mayor que uno, por lo que hay muchos enteros que satisfacen nuestra necesidad.

En realidad, creo que el método de carlosfigueira dará una respuesta “correcta” x en el sentido de que Log(x, 7) tiene la misma representación que Double como 123456.78 . ¿Pero alguien lo ha intentado? 🙂

Voy a dar otra respuesta que ojala sea mas clara. El punto es: ya que la precisión de System.Double se limita a aprox. 15-17 dígitos decimales, el resultado de cualquier cálculo de Pow(BigInteger, Double) tendrá una precisión aún más limitada. Por lo tanto, no hay esperanza de hacerlo mejor que la respuesta de carlosfigueira.

Déjame ilustrar esto con un ejemplo. Supongamos que quisiéramos calcular

 Pow(10, exponent) 

donde en este ejemplo elijo por exponent el número de doble precisión

 const double exponent = 100.0 * Math.PI; 

Esto es, por supuesto, sólo un ejemplo. El valor del exponent , en decimal, se puede dar como uno de

 314.159265358979 314.15926535897933 314.... 

El primero de estos números es lo que normalmente ve (15 dígitos). La segunda versión se produce con exponent.ToString("R") y contiene 17 dígitos. Tenga en cuenta que la precisión de Double es inferior a 17 dígitos. La tercera representación anterior es el valor teórico “exacto” del exponent . Tenga en cuenta que esto difiere, por supuesto, del número matemático 100π cerca del dígito 17.

Para averiguar qué debería ser Pow(10, exponent) , simplemente hice BigInteger.Log10(x) en muchos números x para ver cómo podía reproducir el exponent . Por lo tanto, los resultados presentados aquí simplemente reflejan la implementación de .NET Framework de BigInteger.Log10 .

Resulta que cualquier BigInteger x de

 0x0C3F859904635FC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 through 0x0C3F85990481FE7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 

hace Log10(x) igual a exponent a la precisión de 15 dígitos. Del mismo modo, cualquier número de

 0x0C3F8599047BDEC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 through 0x0C3F8599047D667FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 

satisface Log10(x) == exponent a la precisión de Double . Dicho de otra manera, cualquier número del último rango es igualmente “correcto” como resultado de Pow(10, exponent) , simplemente porque la precisión del exponent es muy limitada.

(Interludio: los grupos de 0 sy F revelan que la implementación de .NET solo considera los bytes más significativos de x . No les importa hacerlo mejor, precisamente porque el tipo Double tiene esta precisión limitada).

Ahora, la única razón para introducir software de terceros, sería si usted insiste en que el exponent debe interpretarse como el tercero de los números decimales dados anteriormente. (Es realmente un milagro que el tipo Double te permitiera especificar exactamente el número que querías, ¿eh?) En ese caso, el resultado de Pow(10, exponent) sería un número irracional (pero algebraico) con una cola de nunca repitiendo decimales. No cabía en un entero sin redondear / truncar. ¡PD! Si tomamos el exponente como el número real 100π, el resultado, matemáticamente, sería diferente: un número trascendental, sospecho.