Saltar al contenido

Explicación aritmética de precisión arbitraria

Comprende el código de forma correcta previamente a usarlo a tu trabajo y si ttienes algo que aportar puedes comentarlo.

Solución:

Todo es cuestión de almacenamiento y algoritmos adecuados para tratar los números como partes más pequeñas. Supongamos que tiene un compilador en el que un int solo puede ser del 0 al 99 y desea manejar números hasta 999999 (aquí solo nos preocuparemos por los números positivos para que sea más simple).

Haces eso dando a cada número tres intsy usando las mismas reglas que (debería haber) aprendido en la escuela primaria para sumar, restar y otras operaciones básicas.

En una biblioteca de precisión arbitraria, no hay un límite fijo en la cantidad de tipos base utilizados para representar nuestros números, solo lo que la memoria pueda contener.

Adición por ejemplo: 123456 + 78:

12 34 56
      78
-- -- --
12 35 34

Trabajando desde el extremo menos significativo:

  • acarreo inicial = 0.
  • 56 + 78 + 0 acarreo = 134 = 34 con 1 acarreo
  • 34 + 00 + 1 acarreo = 35 = 35 con 0 acarreo
  • 12 + 00 + 0 acarreo = 12 = 12 con 0 acarreo

De hecho, así es como funciona generalmente la suma a nivel de bits dentro de su CPU.

La resta es similar (usando la resta del tipo base y pedir prestado en lugar de llevar), la multiplicación se puede hacer con sumas repetidas (muy lento) o productos cruzados (más rápido) y la división es más complicada, pero se puede hacer cambiando y restando los números. involucrados (la división larga que habrías aprendido de niño).

De hecho, he escrito bibliotecas para hacer este tipo de cosas usando las potencias máximas de diez que pueden encajar en un número entero cuando se eleva al cuadrado (para evitar el desbordamiento al multiplicar dos ints juntos, como un 16 bits int estar limitado de 0 a 99 para generar 9.801 (<32.768) cuando se eleva al cuadrado, o 32 bits int usando 0 a 9,999 para generar 99,980,001 (<2,147,483,648)) lo que facilitó enormemente los algoritmos.

Algunos trucos a tener en cuenta.

1 / Al sumar o multiplicar números, pre-asigne el espacio máximo necesario y luego reduzca más tarde si encuentra que es demasiado. Por ejemplo, agregar dos “dígitos” de 100 (donde el dígito es un int) los números nunca le darán más de 101 dígitos. Multiplicar un número de 12 dígitos por un número de 3 dígitos nunca generará más de 15 dígitos (sume los recuentos de dígitos).

2 / Para mayor velocidad, normalice (reduzca el almacenamiento requerido para) los números solo si es absolutamente necesario; mi biblioteca tenía esto como una llamada separada para que el usuario pueda decidir entre problemas de velocidad y almacenamiento.

3 / La suma de un número positivo y negativo es una resta, y restar un número negativo es lo mismo que sumar el equivalente positivo. Puede guardar bastante código haciendo que los métodos de suma y resta se llamen entre sí después de ajustar los signos.

4 / Evite restar números grandes de pequeños, ya que invariablemente terminará con números como:

         10
         11-
-- -- -- --
99 99 99 99 (and you still have a borrow).

En su lugar, reste 10 de 11, luego néguelo:

11
10-
--
 1 (then negate to get -1).

Aquí están los comentarios (convertidos en texto) de una de las bibliotecas para las que tuve que hacer esto. Desafortunadamente, el código en sí está protegido por derechos de autor, pero es posible que pueda seleccionar suficiente información para manejar las cuatro operaciones básicas. Suponga en lo siguiente que -a y -b representan números negativos y a y b son cero o números positivos.

Para adición, si los signos son diferentes, use la resta de la negación:

-a +  b becomes b - a
 a + -b becomes a - b

Para sustracción, si los signos son diferentes, use la suma de la negación:

 a - -b becomes   a + b
-a -  b becomes -(a + b)

También un manejo especial para asegurarnos de que restamos números pequeños de grandes:

small - big becomes -(big - small)

Multiplicación usa matemáticas de nivel de entrada de la siguiente manera:

475(a) x 32(b) = 475 x (30 + 2)
               = 475 x 30 + 475 x 2
               = 4750 x 3 + 475 x 2
               = 4750 + 4750 + 4750 + 475 + 475

La forma en que esto se logra implica extraer cada uno de los dígitos de 32 uno a la vez (hacia atrás) y luego usar sumar para calcular un valor que se agregará al resultado (inicialmente cero).

ShiftLeft y ShiftRight Las operaciones se utilizan para multiplicar o dividir rápidamente un LongInt por el valor de ajuste (10 para matemáticas “reales”). En el ejemplo anterior, sumamos 475 a cero 2 veces (el último dígito de 32) para obtener 950 (resultado = 0 + 950 = 950).

Luego, cambiamos a la izquierda 475 para obtener 4750 y a la derecha 32 para obtener 3. Suma 4750 a cero 3 veces para obtener 14250 y luego suma al resultado de 950 para obtener 15200.

Desplazamiento a la izquierda 4750 para obtener 47500, desplazamiento a la derecha 3 para obtener 0. Dado que el desplazamiento a la derecha 32 ahora es cero, hemos terminado y, de hecho, 475 x 32 es igual a 15200.

División también es complicado pero se basa en la aritmética temprana (el método “gazinta” para “entra”). Considere la siguiente división larga para 12345 / 27:

       457
   +-------
27 | 12345    27 is larger than 1 or 12 so we first use 123.
     108      27 goes into 123 4 times, 4 x 27 = 108, 123 - 108 = 15.
     ---
      154     Bring down 4.
      135     27 goes into 154 5 times, 5 x 27 = 135, 154 - 135 = 19.
      ---
       195    Bring down 5.
       189    27 goes into 195 7 times, 7 x 27 = 189, 195 - 189 = 6.
       ---
         6    Nothing more to bring down, so stop.

Por lo tanto 12345 / 27 es 457 con resto 6. Verificar:

  457 x 27 + 6
= 12339    + 6
= 12345

Esto se implementa mediante el uso de una variable de reducción (inicialmente cero) para reducir los segmentos de 12345 uno a la vez hasta que sea mayor o igual a 27.

Luego, simplemente restamos 27 de eso hasta que lleguemos por debajo de 27: el número de restas es el segmento agregado a la línea superior.

Cuando no hay más segmentos para derribar, tenemos nuestro resultado.


Tenga en cuenta que estos son algoritmos bastante básicos. Hay formas mucho mejores de hacer aritmética compleja si sus números van a ser particularmente grandes. Puede buscar algo como la Biblioteca aritmética de precisión múltiple de GNU: es sustancialmente mejor y más rápido que mis propias bibliotecas.

Tiene el desafortunado error de que simplemente se cerrará si se queda sin memoria (una falla bastante fatal para una biblioteca de propósito general en mi opinión) pero, si puede mirar más allá de eso, es bastante bueno en lo que hace.

Si no puede usarlo por razones de licencia (o porque no desea que su aplicación simplemente salga sin razón aparente), al menos podría obtener los algoritmos de allí para integrarlos en su propio código.

También descubrí que los cuerpos de MPIR (una bifurcación de GMP) son más receptivos a las discusiones sobre cambios potenciales; parecen un grupo más amigable para los desarrolladores.

Si bien reinventar la rueda es extremadamente bueno para su edificación y aprendizaje personal, también es una tarea extremadamente grande. No quiero disuadirlo, ya que es un ejercicio importante y uno que yo mismo he hecho, pero debe tener en cuenta que hay problemas sutiles y complejos en el trabajo que los paquetes más grandes abordan.

Por ejemplo, multiplicación. Ingenuamente, podría pensar en el método del ‘colegial’, es decir, escribir un número encima del otro y luego hacer una multiplicación larga como aprendió en la escuela. ejemplo:

      123
    x  34
    -----
      492
+    3690
---------
     4182

pero este método es extremadamente lento (O (n ^ 2), siendo n el número de dígitos). En cambio, los paquetes bignum modernos utilizan una transformada de Fourier discreta o una transformada numérica para convertir esto en una operación esencialmente O (n ln (n)).

Y esto es solo para números enteros. Cuando entra en funciones más complicadas en algún tipo de representación real de número (log, sqrt, exp, etc.), las cosas se complican aún más.

Si desea algo de base teórica, le recomiendo que lea el primer capítulo del libro de Yap, “Problemas fundamentales del álgebra algorítmica”. Como ya se mencionó, la biblioteca gmp bignum es una excelente biblioteca. Para números reales, he usado MPFR y me gustó.

No reinventes la rueda: ¡puede resultar cuadrada!

Utilice una biblioteca de terceros, como GNU MP, que esté probada y comprobada.

valoraciones y comentarios

Si haces scroll puedes encontrar las interpretaciones de otros creadores, tú de igual manera puedes dejar el tuyo si lo crees conveniente.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *