Necesito calcular una expresión que se vea así:
A*B - C*D
donde 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*B
podría causar un desbordamiento, al mismo tiempo, la expresión A*B - C*D
puede ser realmente pequeña. ¿Cómo puedo calcularlo correctamente?
Por ejemplo:, MAX * MAX - (MAX - 1) * (MAX + 1) == 1
donde MAX = LLONG_MAX - n
y n - algún número natural.
c++
c
integer-overflow
NGix
fuente
fuente
A - C
podrí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*B
es 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*D
A,B,C,D
es negativo? ¿No seráE
oF
será 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 long
donde 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
,C
yD
pasar 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 double
está 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
123
se 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 int
no se mantendráA*B
, dos de ellos lo harán. PorA*B
lo 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_i
y de laCD_i
misma 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_10
aE_20
(cambia por 32 y suma, luego borra la mitad superior deE_10
).Ahora puede deshacerse del bit de acarreo
E_11
agregándolo con el signo correcto (obtenido de la parte no transportadora) aE_20
. Si esto desencadena un desbordamiento, el resultado tampoco encajaría.E_10
ahora tiene suficiente 'espacio' para tomar la mitad superior deE_00
(shift, add, borre) y el bit de acarreoE_01
.E_10
puede ser más grande ahora nuevamente, así que repetimos la transferencia aE_20
.En este punto,
E_20
debe convertirse en cero, de lo contrario el resultado no encajará. La mitad superior deE_10
está vacía como resultado de la transferencia también.El paso final es la transferencia de la mitad inferior de
E_20
enE_10
otra vez.Si la expectativa
E=A*B+C*D
se ajustara a lassigned long long int
bodegas, 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 char
yunsigned char
para demostrar el código. Para su implementación, cambie la definición deSigned
totypedef signed long long int Signed;
y la definición deUnsigned
totypedef 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
K
yJ
, por qué noN
yM
. 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/C
tampocoD/A
puede 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*C
cuál es el resultado requerido.Tenga en cuenta que si su entrada también puede ser extremadamente pequeña ,
B/C
oD/A
puede 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-C
yB-D
son números pequeños.fuente
A-C+B-D
es 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)
:)