Cómo implementar big int en C++

Resuelto oneself asked hace 15 años • 14 respuestas

Me gustaría implementar una clase int grande en C++ como ejercicio de programación: una clase que pueda manejar números mayores que un int largo. Sé que ya existen varias implementaciones de código abierto, pero me gustaría escribir la mía propia. Estoy tratando de tener una idea de cuál es el enfoque correcto.

Entiendo que la estrategia general es obtener el número como una cadena y luego dividirlo en números más pequeños (un solo dígito, por ejemplo) y colocarlos en una matriz. En este punto debería ser relativamente sencillo implementar los distintos operadores de comparación. Mi principal preocupación es cómo implementaría cosas como la suma y la multiplicación.

Estoy buscando un enfoque general y consejos en lugar de un código de trabajo real.

oneself avatar Nov 06 '08 23:11 oneself
Aceptado

Un desafío divertido. :)

Supongo que quieres números enteros de longitud arbitraria. Sugiero el siguiente enfoque:

Considere la naturaleza binaria del tipo de datos "int". Piense en utilizar operaciones binarias simples para emular lo que hacen los circuitos de su CPU cuando agregan cosas. En caso de que esté interesado en profundizar más, considere leer este artículo de Wikipedia sobre semisumadores y sumadores completos . Harás algo similar a eso, pero puedes bajar a un nivel tan bajo como ese, pero como soy vago, pensé en renunciar y encontrar una solución aún más simple.

Pero antes de entrar en detalles algorítmicos sobre sumar, restar y multiplicar, busquemos alguna estructura de datos. Una forma sencilla es, por supuesto, almacenar cosas en un std::vector.

template< class BaseType >
class BigInt
{
typedef typename BaseType BT;
protected: std::vector< BaseType > value_;
};

Es posible que desee considerar si desea que el vector tenga un tamaño fijo y si desea preasignarlo. La razón es que para diversas operaciones, tendrá que pasar por cada elemento del vector: O (n). Es posible que desee saber de antemano qué tan compleja será una operación y una n fija hace precisamente eso.

Pero ahora veamos algunos algoritmos para operar con números. Podrías hacerlo a nivel lógico, pero usaremos esa potencia mágica de la CPU para calcular los resultados. Pero lo que tomaremos de la ilustración lógica de Half- y FullAdders es la forma en que trata los acarreos. Como ejemplo, considere cómo implementaría el operador += . Para cada número en BigInt<>::value_, los agregaría y vería si el resultado produce alguna forma de acarreo. No lo haremos bit a bit, sino que confiaremos en la naturaleza de nuestro BaseType (ya sea largo, int, corto o lo que sea): se desborda.

Seguramente, si sumas dos números, el resultado debe ser mayor que el mayor de esos números, ¿verdad? Si no es así, entonces el resultado se desbordó.

template< class BaseType >
BigInt< BaseType >& BigInt< BaseType >::operator += (BigInt< BaseType > const& operand)
{
  BT count, carry = 0;
  for (count = 0; count < std::max(value_.size(), operand.value_.size(); count++)
  {
    BT op0 = count < value_.size() ? value_.at(count) : 0, 
       op1 = count < operand.value_.size() ? operand.value_.at(count) : 0;
    BT digits_result = op0 + op1 + carry;
    if (digits_result-carry < std::max(op0, op1)
    {
      BT carry_old = carry;
      carry = digits_result;
      digits_result = (op0 + op1 + carry) >> sizeof(BT)*8; // NOTE [1]
    }
    else carry = 0;
  }

  return *this;
}
// NOTE 1: I did not test this code. And I am not sure if this will work; if it does
//         not, then you must restrict BaseType to be the second biggest type 
//         available, i.e. a 32-bit int when you have a 64-bit long. Then use
//         a temporary or a cast to the mightier type and retrieve the upper bits. 
//         Or you do it bitwise. ;-)

La otra operación aritmética es análoga. Diablos, incluso podrías usar los functores stl std::plus y std::minus, std::times y std::divides, ..., pero ten cuidado con el transporte. :) También puedes implementar la multiplicación y la división usando los operadores más y menos, pero eso es muy lento, porque volvería a calcular los resultados que ya calculaste en llamadas anteriores a más y menos en cada iteración. Existen muchos algoritmos buenos para esta sencilla tarea; utilice wikipedia o la web.

Y, por supuesto, debes implementar operadores estándar como operator<<(simplemente desplaza cada valor en value_ hacia la izquierda durante n bits, comenzando en value_.size()-1... ah, y recuerda el acarreo :), operator<- incluso puedes optimizar un poco aquí, verificando el número aproximado de dígitos con size()el primero. Etcétera. Luego haz que tu clase sea útil, haciéndote amigo de std::ostream operator<<.

¡Espero que este enfoque sea útil!

mstrobl avatar Nov 06 '2008 20:11 mstrobl

Cosas a considerar para una gran clase int:

  1. Operadores matemáticos: +, -, /, *, % No olvides que tu clase puede estar a ambos lados del operador, que los operadores pueden estar encadenados, que uno de los operandos puede ser int, float, double, etc. .

  2. Operadores de E/S: >>, << Aquí es donde descubrirás cómo crear correctamente tu clase a partir de la entrada del usuario y también cómo formatearla para la salida.

  3. Conversiones/Casts: averigüe a qué tipos/clases su clase int grande debería ser convertible y cómo manejar adecuadamente la conversión. Una lista rápida incluiría double y float, y puede incluir int (con una verificación de límites adecuada) y complex (suponiendo que pueda manejar el rango).

Harper Shelby avatar Nov 06 '2008 16:11 Harper Shelby

Hay una sección completa sobre esto: [El arte de la programación informática, vol.2: Algoritmos seminuméricos, sección 4.3 Aritmética de precisión múltiple, págs. 265-318 (ed.3)]. Puede encontrar otro material interesante en el Capítulo 4, Aritmética.

Si realmente no desea ver otra implementación, ¿ha considerado qué es lo que desea aprender? Se pueden cometer innumerables errores y descubrirlos es instructivo y también peligroso. También existen desafíos para identificar economías computacionales importantes y tener estructuras de almacenamiento adecuadas para evitar problemas graves de rendimiento.

Una pregunta desafiante para usted: ¿Cómo piensa probar su implementación y cómo propone demostrar que su aritmética es correcta?

Es posible que desee realizar pruebas con otra implementación (sin observar cómo lo hace), pero se necesitará más que eso para poder generalizar sin esperar un nivel de prueba insoportable. No olvide considerar los modos de falla (problemas de falta de memoria, falta de pila, ejecución demasiado prolongada, etc.).

¡Divertirse!

orcmid avatar Nov 06 '2008 20:11 orcmid

Probablemente la suma tendría que hacerse en el algoritmo de tiempo lineal estándar
, pero para la multiplicación puedes probar http://en.wikipedia.org/wiki/Karatsuba_algorithm

Aditya Mukherji avatar Nov 06 '2008 16:11 Aditya Mukherji