Necesito calcular una expresión que se vea así:
A*B - C*Ddonde están sus tipos: signed long long int A, B, C, D;
cada número puede ser realmente grande (sin desbordar su tipo). Si bien A*Bpodría causar un desbordamiento, al mismo tiempo, la expresión A*B - C*Dpuede ser realmente pequeña. ¿Cómo puedo calcularlo correctamente?
Por ejemplo:, MAX * MAX - (MAX - 1) * (MAX + 1) == 1donde MAX = LLONG_MAX - ny n - algún número natural.
c++
c
integer-overflow
NGix
fuente
fuente

A - Cpodría desbordarse. ¿Es un problema a tener en cuenta o sabes que esto no va a suceder con tus datos?Respuestas:
Esto parece demasiado trivial, supongo. Pero
A*Bes el que podría desbordarse.Podrías hacer lo siguiente, sin perder precisión
Esta descomposición puede hacerse más .
Como señaló @Gian, es posible que sea necesario tener cuidado durante la operación de sustracción si el tipo no está firmado durante mucho tiempo.
Por ejemplo, con el caso que tiene en la pregunta, solo se necesita una iteración,
fuente
C*DA,B,C,Des negativo? ¿No seráEoFserá aún más grande entonces?La solución más simple y más general es usar una representación que no pueda desbordarse, ya sea usando una biblioteca de enteros largos (por ejemplo, http://gmplib.org/ ) o representando usando una estructura o matriz e implementando una especie de multiplicación larga ( es decir, separar cada número en dos mitades de 32 bits y realizar la multiplicación de la siguiente manera:
Suponiendo que el resultado final se ajusta a 64 bits, en realidad no necesita la mayoría de los bits de R3 y ninguno de R4
fuente
Tenga en cuenta que esto no es estándar, ya que se basa en un desbordamiento con firma envolvente. (GCC tiene indicadores de compilación que permiten esto).
Pero si solo haces todos los cálculos en
long long, el resultado de aplicar la fórmula directamente:(A * B - C * D)será preciso siempre que el resultado correcto se ajuste a along long.Aquí hay una solución alternativa que solo se basa en el comportamiento definido por la implementación de convertir un entero sin signo en un entero con signo. Pero se puede esperar que esto funcione en casi todos los sistemas hoy.
Esto arroja las entradas a
unsigned long longdonde el comportamiento de desbordamiento está garantizado por el estándar. Volver a un número entero firmado al final es la parte definida por la implementación, pero funcionará en casi todos los entornos hoy.Si necesita una solución más pedante, creo que debe usar "aritmética larga"
fuente
long long.Esto debería funcionar (creo):
Aquí está mi derivación:
fuente
Podría considerar calcular el mayor factor común para todos sus valores, y luego dividirlos por ese factor antes de realizar sus operaciones aritméticas, y luego multiplicar nuevamente. Esto supone que existe tal factor a, sin embargo (por ejemplo, si
A,B,CyDpasar a ser primos entre sí, no van a tener un factor común).Del mismo modo, podría considerar trabajar en escalas logarítmicas, pero esto va a ser un poco aterrador, sujeto a precisión numérica.
fuente
long doubleestá disponible. En ese caso, se puede lograr un nivel aceptable de precisión (y el resultado puede redondearse).Si el resultado cabe en un largo largo int entonces la expresión A * BC * D está bien ya que realiza el mod aritmético 2 ^ 64, y dará el resultado correcto. El problema es saber si el resultado cabe en un largo largo int. Para detectar esto, puedes usar el siguiente truco usando dobles:
El problema con este enfoque es que está limitado por la precisión de la mantisa de los dobles (¿54 bits?), Por lo que debe limitar los productos A * B y C * D a 63 + 54 bits (o probablemente un poco menos).
fuente
luego
fuente
Puede escribir cada número en una matriz, cada elemento es un dígito y hacer los cálculos como polinomios . Tome el polinomio resultante, que es una matriz, y calcule el resultado multiplicando cada elemento de la matriz con 10 a la potencia de la posición en la matriz (la primera posición es la más grande y la última es cero).
El número
123se puede expresar como:para lo cual solo creas una matriz
[1 2 3].Hace esto para todos los números A, B, C y D, y luego los multiplica como polinomios. Una vez que tenga el polinomio resultante, simplemente reconstruya el número a partir de él.
fuente
Si bien a
signed long long intno se mantendráA*B, dos de ellos lo harán. PorA*Blo tanto, podría descomponerse en términos de árbol de exponente diferente, cualquiera de ellos adecuadosigned long long int.Lo mismo para
C*D.Siguiendo el camino recto, la subtracción se podría hacer a cada par de,
AB_iy de laCD_imisma manera, usando un bit de acarreo adicional (con precisión un entero de 1 bit) para cada uno. Entonces, si decimos E = A * BC * D, obtienes algo como:Continuamos transfiriendo la mitad superior de
E_10aE_20(cambia por 32 y suma, luego borra la mitad superior deE_10).Ahora puede deshacerse del bit de acarreo
E_11agregándolo con el signo correcto (obtenido de la parte no transportadora) aE_20. Si esto desencadena un desbordamiento, el resultado tampoco encajaría.E_10ahora tiene suficiente 'espacio' para tomar la mitad superior deE_00(shift, add, borre) y el bit de acarreoE_01.E_10puede ser más grande ahora nuevamente, así que repetimos la transferencia aE_20.En este punto,
E_20debe convertirse en cero, de lo contrario el resultado no encajará. La mitad superior deE_10está vacía como resultado de la transferencia también.El paso final es la transferencia de la mitad inferior de
E_20enE_10otra vez.Si la expectativa
E=A*B+C*Dse ajustara a lassigned long long intbodegas, ahora tenemosfuente
Si sabe que el resultado final es representable en su tipo de entero, puede realizar este cálculo rápidamente utilizando el código a continuación. Debido a que el estándar C especifica que la aritmética sin signo es módulo aritmético y no se desborda, puede usar un tipo sin signo para realizar el cálculo.
El siguiente código supone que hay un tipo sin signo del mismo ancho y que el tipo con signo utiliza todos los patrones de bits para representar valores (sin representaciones de trampa, el mínimo del tipo con signo es el negativo de la mitad del módulo del tipo sin signo). Si esto no se cumple en una implementación en C, se pueden hacer ajustes simples a la rutina ConvertToSigned para eso.
Los siguientes usos
signed charyunsigned charpara demostrar el código. Para su implementación, cambie la definición deSignedtotypedef signed long long int Signed;y la definición deUnsignedtotypedef unsigned long long int Unsigned;.fuente
Podría intentar dividir la ecuación en componentes más pequeños que no se desborden.
Si los componentes aún se desbordan, podría dividirlos en componentes más pequeños de forma recursiva y luego recombinarlos.
fuente
KyJ, por qué noNyM. Además, creo que estás rompiendo la ecuación en pedazos más grandes. Dado que su paso 3 es el mismo que la pregunta del OP, excepto que es más complicado(AK-CJ)->(AB-CD)Es posible que no haya cubierto todos los casos límite, ni lo he probado rigurosamente, pero esto implementa una técnica que recuerdo haber usado en los años 80 al intentar hacer cálculos enteros de 32 bits en una CPU de 16 bits. Básicamente, divide los 32 bits en dos unidades de 16 bits y trabaja con ellos por separado.
Huellas dactilares:
lo cual me parece que está funcionando.
Apuesto a que me he perdido algunas de las sutilezas, como observar el desbordamiento de signos, etc., pero creo que la esencia está ahí.
fuente
En aras de la exhaustividad, ya que nadie lo mencionó, algunos compiladores (por ejemplo, GCC) realmente le proporcionan un número entero de 128 bits hoy en día.
Por lo tanto, una solución fácil podría ser:
fuente
AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. NiB/CtampocoD/Apuede desbordarse, así que calcule(B/C-D/A)primero. Dado que el resultado final no se desbordará según su definición, puede realizar con seguridad las multiplicaciones restantes y calcular(B/C-D/A)*A*Ccuál es el resultado requerido.Tenga en cuenta que si su entrada también puede ser extremadamente pequeña ,
B/CoD/Apuede desbordarse. Si es posible, se pueden requerir manipulaciones más complejas de acuerdo con la inspección de entrada.fuente
Elija
K = a big number(p. Ej.K = A - sqrt(A))¿Por qué?
Nótese que debido a A, B, C y D son números grandes, por lo tanto
A-CyB-Dson números pequeños.fuente
A-C+B-Des un número pequeño. Debido a que A, B, C y D son números grandes, entonces AC es un número pequeño.A - sqrt(A):)