Cómo evitar el desbordamiento en expr. A B C D


161

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.


17
¿Qué tan importante es la precisión?
Anirudh Ramanathan

1
@Cthulhu, gran pregunta. Podría intentar hacer una función equivalente usando un número menor dividiéndolos por 10 o algo así, y luego multiplicando el resultado.
Chris

44
Vars A, B, C, D están firmados. Esto implica que A - Cpodría desbordarse. ¿Es un problema a tener en cuenta o sabes que esto no va a suceder con tus datos?
William Morris

2
@MooingDuck pero puede verificar de antemano si la operación se desbordará stackoverflow.com/a/3224630/158285
bradgonesurfing

1
@ Chris: No, estoy diciendo que no hay una forma portátil de verificar si se ha producido un desbordamiento firmado. (Brad es correcto que se puede detectar de forma portátil que va a pasar). El uso del ensamblaje en línea es una de las muchas formas no portátiles de verificar.
Mooing Duck

Respuestas:


120

Esto parece demasiado trivial, supongo. Pero A*Bes el que podría desbordarse.

Podrías hacer lo siguiente, sin perder precisión

A*B - C*D = A(D+E) - (A+F)D
          = AD + AE - AD - DF
          = AE - DF
             ^smaller quantities E & F

E = B - D (hence, far smaller than B)
F = C - A (hence, far smaller than C)

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,

 MAX * MAX - (MAX - 1) * (MAX + 1)
  A     B       C           D

E = B - D = -1
F = C - A = -1

AE - DF = {MAX * -1} - {(MAX + 1) * -1} = -MAX + MAX + 1 = 1

44
@Caleb, solo aplica el mismo algoritmo aC*D
Chris

2
Creo que deberías explicar lo que E representa.
Caleb

77
Tanto long long como double son 64 bits. Como double tiene que asignar algunos bits para el exponente, tiene un rango más pequeño de valores posibles sin pérdida de precisión.
Jim Garrison el

3
@Cthulhu: me parece que esto solo funcionaría si todos los números son muy grandes ... por ejemplo, todavía se desbordaría con {A, B, C, D} = {MAX, MAX, MAX, 2}. El OP dice "Cada número puede ser realmente grande", pero no está claro en la declaración del problema que cada número debe ser realmente grande.
Kevin K

44
¿Qué pasa si alguno de ellos A,B,C,Des negativo? ¿No será Eo Fserá aún más grande entonces?
Supr

68

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:

(R1 + R2 * 2^32 + R3 * 2^64 + R4 * 2^96) = R = A*B = (A1 + A2 * 2^32) * (B1 + B2 * 2^32) 
R1 = (A1*B1) % 2^32
R2 = ((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) % 2^32
R3 = (((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) %2^32
R4 = ((((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) / 2^32) + (A2*B2) / 2^32

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


8
El cálculo anterior no es realmente tan complicado como parece, realmente es una simple multiplicación larga en la base 2 ^ 32, y el código en C debería verse más simple. Además, será una buena idea crear funciones genéricas para hacer este trabajo en su programa.
Ofir

46

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 a long 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.

(long long)((unsigned long long)A * B - (unsigned long long)C * D)

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"


+1 Eres el único en notar esto. La única parte difícil es configurar el compilador para hacer un desbordamiento envolvente y verificar si el resultado correcto realmente encaja en a long long.
Mysticial

2
Incluso la versión ingenua sin ningún truco hará lo correcto en la mayoría de las implementaciones; no está garantizado por el estándar, pero tendrías que encontrar una máquina de complemento de 1 o algún otro dispositivo bastante extraño para que falle.
hobbs

1
Creo que esta es una respuesta importante. Estoy de acuerdo en que puede no ser una programación correcta asumir un comportamiento específico de implementación, pero cada ingeniero debe comprender la aritmética de módulos y cómo obtener los indicadores del compilador correctos para garantizar un comportamiento consistente si el rendimiento es esencial. Los ingenieros de DSP confían en este comportamiento para las implementaciones de filtros de punto fijo, para las cuales la respuesta aceptada tendrá un rendimiento inaceptable.
Peter M

18

Esto debería funcionar (creo):

signed long long int a = 0x7ffffffffffffffd;
signed long long int b = 0x7ffffffffffffffd;
signed long long int c = 0x7ffffffffffffffc;
signed long long int d = 0x7ffffffffffffffe;
signed long long int bd = b / d;
signed long long int bdmod = b % d;
signed long long int ca = c / a;
signed long long int camod = c % a;
signed long long int x = (bd - ca) * a * d - (camod * d - bdmod * a);

Aquí está mi derivación:

x = a * b - c * d
x / (a * d) = (a * b - c * d) / (a * d)
x / (a * d) = b / d - c / a

now, the integer/mod stuff:
x / (a * d) = (b / d + ( b % d ) / d) - (c / a + ( c % a ) / a )
x / (a * d) = (b / d - c / a) - ( ( c % a ) / a - ( b % d ) / d)
x = (b / d - c / a) * a * d - ( ( c % a ) * d - ( b % d ) * a)

1
Gracias @bradgonesurfing. ¿Podrías proporcionarnos esa información? He actualizado mi respuesta, la
ejecuté

1
Hmmm Ahora lo pienso, tal vez no. Degenerar mayúsculas y minúsculas con d = 1 y a = 1 y b = maxint y c = maxint todavía funciona. Genial :)
bradgonesurfing

1
@paquetp: a = 1, b = 0x7fffffffffffffff, c = -0x7fffffffffffffff, d = 1 (nota c es negativa). Aunque inteligente, estoy seguro de que su código maneja todos los números positivos correctamente.
Mooing Duck

3
@MooingDuck, pero la respuesta final para su conjunto también se desborda, por lo que no es una configuración válida. Solo funciona si cada lado tiene el mismo signo, por lo que la resta resultante está dentro del rango.
bradgonesurfing

1
Hay algo extraño con StackOverflow cuando esta respuesta, que es la más simple y la mejor, tiene una puntuación tan baja en comparación con la respuesta mejor calificada.
bradgonesurfing

9

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, CyD 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.


1
El logaritmo parece bueno si long doubleestá disponible. En ese caso, se puede lograr un nivel aceptable de precisión (y el resultado puede redondearse).

9

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:

if( abs( (double)A*B - (double)C*D ) > MAX_LLONG ) 
    Overflow
else 
    return A*B-C*D;

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).


Este es el ejemplo más práctico. Despeja y da la respuesta correcta (o lanza una excepción cuando las entradas son malas).
Mark Lakata

1
Agradable y elegante! No caíste en la trampa por la que cayeron los demás. Solo una cosa más: apuesto a que hay algunos ejemplos en los que el cálculo doble está por debajo de MAX_LLONG solo debido a errores de redondeo. Mi instinto matemático me dice que deberías calcular la diferencia del resultado doble y largo en su lugar, y compararlo con MAX_LLONG / 2 o algo así. Esta diferencia son los errores de redondeo del cálculo doble y el desbordamiento y normalmente deberían ser relativamente bajos, pero en el caso que mencioné será grande. Pero en este momento soy demasiado vago para saberlo con certeza. :-)
Hans-Peter Störr

9
E = max(A,B,C,D)
A1 = A -E;
B1 = B -E;
C1 = C -E;
D1 = D -E;

luego

A*B - C*D = (A1+E)*(B1+E)-(C1+E)(D1+E) = (A1+B1-C1-D1)*E + A1*B1 -C1*D1

7

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:

123 = 100 * 1 + 10 * 2 + 3

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.


2
No sé qué es eso, pero tendré que encontrarlo. poner :) . esta es una solución de mi cabeza mientras estoy de compras con mi novia :)
Mihai

estás implementando bignums en una matriz base10. GMP es una biblioteca bignum de calidad que utiliza la base 4294967296. MUCHO más rápido. Sin embargo, no hay voto negativo, porque la respuesta es correcta y útil.
Mooing Duck

Gracias :) . Es útil saber que esta es una forma de hacerlo, pero hay mejores maneras, así que no lo hagas así. al menos no en esta situación :)
Mihai

de todos modos ... usando esta solución, podría calcular un número mucho mayor que cualquier tipo primitivo en negrita (como números de 100 dígitos) y mantener el resultado como una matriz. esto merece un voto positivo: p
Mihai

No estoy seguro de que reciba un voto positivo, ya que este método (aunque efectivo y relativamente fácil de entender) consume mucha memoria y es lento.
Mooing Duck

6

Si bien a signed long long intno se mantendrá A*B, dos de ellos lo harán. Por A*Blo tanto, podría descomponerse en términos de árbol de exponente diferente, cualquiera de ellos adecuado signed long long int.

A1=A>>32;
A0=A & 0xffffffff;
B1=B>>32;
B0=B & 0xffffffff;

AB_0=A0*B0;
AB_1=A0*B1+A1*B0;
AB_2=A1*B1;

Lo mismo para C*D.

Siguiendo el camino recto, la subtracción se podría hacer a cada par de, AB_iy de la CD_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:

E_00=AB_0-CD_0 
E_01=(AB_0 > CD_0) == (AB_0 - CD_0 < 0) ? 0 : 1  // carry bit if overflow
E_10=AB_1-CD_1 
...

Continuamos transfiriendo la mitad superior de E_10a E_20(cambia por 32 y suma, luego borra la mitad superior de E_10).

Ahora puede deshacerse del bit de acarreo E_11agregándolo con el signo correcto (obtenido de la parte no transportadora) a E_20. Si esto desencadena un desbordamiento, el resultado tampoco encajaría.

E_10ahora tiene suficiente 'espacio' para tomar la mitad superior de E_00 (shift, add, borre) y el bit de acarreo E_01.

E_10puede ser más grande ahora nuevamente, así que repetimos la transferencia a E_20.

En este punto, E_20debe convertirse en cero, de lo contrario el resultado no encajará. La mitad superior de E_10está vacía como resultado de la transferencia también.

El paso final es la transferencia de la mitad inferior de E_20en E_10otra vez.

Si la expectativa E=A*B+C*Dse ajustara a las signed long long intbodegas, ahora tenemos

E_20=0
E_10=0
E_00=E

1
Esta es en realidad la fórmula simplificada que se obtendría si se usa la fórmula de multiplicación de Ofir y se eliminan todos los resultados temporales inútiles.
Dronus

3

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 chary unsigned charpara demostrar el código. Para su implementación, cambie la definición de Signedto typedef signed long long int Signed;y la definición de Unsignedto typedef unsigned long long int Unsigned;.

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>


//  Define the signed and unsigned types we wish to use.
typedef signed char   Signed;
typedef unsigned char Unsigned;

//  uHalfModulus is half the modulus of the unsigned type.
static const Unsigned uHalfModulus = UCHAR_MAX/2+1;

//  sHalfModulus is the negation of half the modulus of the unsigned type.
static const Signed   sHalfModulus = -1 - (Signed) (UCHAR_MAX/2);


/*  Map the unsigned value to the signed value that is the same modulo the
    modulus of the unsigned type.  If the input x maps to a positive value, we
    simply return x.  If it maps to a negative value, we return x minus the
    modulus of the unsigned type.

    In most C implementations, this routine could simply be "return x;".
    However, this version uses several steps to convert x to a negative value
    so that overflow is avoided.
*/
static Signed ConvertToSigned(Unsigned x)
{
    /*  If x is representable in the signed type, return it.  (In some
        implementations, 
    */
    if (x < uHalfModulus)
        return x;

    /*  Otherwise, return x minus the modulus of the unsigned type, taking
        care not to overflow the signed type.
    */
    return (Signed) (x - uHalfModulus) - sHalfModulus;
}


/*  Calculate A*B - C*D given that the result is representable as a Signed
    value.
*/
static signed char Calculate(Signed A, Signed B, Signed C, Signed D)
{
    /*  Map signed values to unsigned values.  Positive values are unaltered.
        Negative values have the modulus of the unsigned type added.  Because
        we do modulo arithmetic below, adding the modulus does not change the
        final result.
    */
    Unsigned a = A;
    Unsigned b = B;
    Unsigned c = C;
    Unsigned d = D;

    //  Calculate with modulo arithmetic.
    Unsigned t = a*b - c*d;

    //  Map the unsigned value to the corresponding signed value.
    return ConvertToSigned(t);
}


int main()
{
    //  Test every combination of inputs for signed char.
    for (int A = SCHAR_MIN; A <= SCHAR_MAX; ++A)
    for (int B = SCHAR_MIN; B <= SCHAR_MAX; ++B)
    for (int C = SCHAR_MIN; C <= SCHAR_MAX; ++C)
    for (int D = SCHAR_MIN; D <= SCHAR_MAX; ++D)
    {
        //  Use int to calculate the expected result.
        int t0 = A*B - C*D;

        //  If the result is not representable in signed char, skip this case.
        if (t0 < SCHAR_MIN || SCHAR_MAX < t0)
            continue;

        //  Calculate the result with the sample code.
        int t1 = Calculate(A, B, C, D);

        //  Test the result for errors.
        if (t0 != t1)
        {
            printf("%d*%d - %d*%d = %d, but %d was returned.\n",
                A, B, C, D, t0, t1);
            exit(EXIT_FAILURE);
        }
    }
    return 0;
}

2

Podría intentar dividir la ecuación en componentes más pequeños que no se desborden.

AB - CD
= [ A(B - N) - C( D - M )] + [AN - CM]

= ( AK - CJ ) + ( AN - CM)

    where K = B - N
          J = D - M

Si los componentes aún se desbordan, podría dividirlos en componentes más pequeños de forma recursiva y luego recombinarlos.


Esto puede o no ser correcto, pero definitivamente es confuso. Usted define Ky J, por qué no Ny M. 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)
Mooing Duck

N no se simplifica de nada. Es solo un número restado de A para hacerlo más pequeño. En realidad, es una solución similar pero inferior a paquetp. Aquí estoy usando la resta en lugar de la división entera para hacerlo más pequeño.
bradgonesurfing

2

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.

public class DoubleMaths {
  private static class SplitLong {
    // High half (or integral part).
    private final long h;
    // Low half.
    private final long l;
    // Split.
    private static final int SPLIT = (Long.SIZE / 2);

    // Make from an existing pair.
    private SplitLong(long h, long l) {
      // Let l overflow into h.
      this.h = h + (l >> SPLIT);
      this.l = l % (1l << SPLIT);
    }

    public SplitLong(long v) {
      h = v >> SPLIT;
      l = v % (1l << SPLIT);
    }

    public long longValue() {
      return (h << SPLIT) + l;
    }

    public SplitLong add ( SplitLong b ) {
      // TODO: Check for overflow.
      return new SplitLong ( longValue() + b.longValue() );
    }

    public SplitLong sub ( SplitLong b ) {
      // TODO: Check for overflow.
      return new SplitLong ( longValue() - b.longValue() );
    }

    public SplitLong mul ( SplitLong b ) {
      /*
       * e.g. 10 * 15 = 150
       * 
       * Divide 10 and 15 by 5
       * 
       * 2 * 3 = 5
       * 
       * Must therefore multiply up by 5 * 5 = 25
       * 
       * 5 * 25 = 150
       */
      long lbl = l * b.l;
      long hbh = h * b.h;
      long lbh = l * b.h;
      long hbl = h * b.l;
      return new SplitLong ( lbh + hbl, lbl + hbh );
    }

    @Override
    public String toString () {
      return Long.toHexString(h)+"|"+Long.toHexString(l);
    }
  }

  // I'll use long and int but this can apply just as easily to long-long and long.
  // The aim is to calculate A*B - C*D without overflow.
  static final long A = Long.MAX_VALUE;
  static final long B = Long.MAX_VALUE - 1;
  static final long C = Long.MAX_VALUE;
  static final long D = Long.MAX_VALUE - 2;

  public static void main(String[] args) throws InterruptedException {
    // First do it with BigIntegers to get what the result should be.
    BigInteger a = BigInteger.valueOf(A);
    BigInteger b = BigInteger.valueOf(B);
    BigInteger c = BigInteger.valueOf(C);
    BigInteger d = BigInteger.valueOf(D);
    BigInteger answer = a.multiply(b).subtract(c.multiply(d));
    System.out.println("A*B - C*D = "+answer+" = "+answer.toString(16));

    // Make one and test its integrity.
    SplitLong sla = new SplitLong(A);
    System.out.println("A="+Long.toHexString(A)+" ("+sla.toString()+") = "+Long.toHexString(sla.longValue()));

    // Start small.
    SplitLong sl10 = new SplitLong(10);
    SplitLong sl15 = new SplitLong(15);
    SplitLong sl150 = sl10.mul(sl15);
    System.out.println("10="+sl10.longValue()+"("+sl10.toString()+") * 15="+sl15.longValue()+"("+sl15.toString()+") = "+sl150.longValue() + " ("+sl150.toString()+")");

    // The real thing.
    SplitLong slb = new SplitLong(B);
    SplitLong slc = new SplitLong(C);
    SplitLong sld = new SplitLong(D);
    System.out.println("B="+Long.toHexString(B)+" ("+slb.toString()+") = "+Long.toHexString(slb.longValue()));
    System.out.println("C="+Long.toHexString(C)+" ("+slc.toString()+") = "+Long.toHexString(slc.longValue()));
    System.out.println("D="+Long.toHexString(D)+" ("+sld.toString()+") = "+Long.toHexString(sld.longValue()));
    SplitLong sanswer = sla.mul(slb).sub(slc.mul(sld));
    System.out.println("A*B - C*D = "+sanswer+" = "+sanswer.longValue());

  }

}

Huellas dactilares:

A*B - C*D = 9223372036854775807 = 7fffffffffffffff
A=7fffffffffffffff (7fffffff|ffffffff) = 7fffffffffffffff
10=10(0|a) * 15=15(0|f) = 150 (0|96)
B=7ffffffffffffffe (7fffffff|fffffffe) = 7ffffffffffffffe
C=7fffffffffffffff (7fffffff|ffffffff) = 7fffffffffffffff
D=7ffffffffffffffd (7fffffff|fffffffd) = 7ffffffffffffffd
A*B - C*D = 7fffffff|ffffffff = 9223372036854775807

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í.


1
Creo que esta es una implementación de lo que sugirió @Ofir.
OldCurmudgeon

2

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:

(long long)((__int128)A * B - (__int128)C * D)

1

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Ni B/Ctampoco D/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/Co D/Apuede desbordarse. Si es posible, se pueden requerir manipulaciones más complejas de acuerdo con la inspección de entrada.


2
Eso no funcionará ya que la división entera pierde información (la fracción del resultado)
Ofir

@Ofir eso es correcto, sin embargo, no puedes comer el pastel y dejarlo intacto. Debe pagar con precisión o utilizando recursos adicionales (como sugirió en su respuesta). Mi respuesta es de naturaleza matemática, mientras que la suya está orientada a la computadora. Cada uno puede ser correcto según las circunstancias.
SomeWittyUsername

2
Tienes razón, debería haberlo expresado como, no dará un resultado exacto en lugar de no funcionar, ya que las matemáticas son correctas. Sin embargo, tenga en cuenta que en los casos que probablemente interesen al remitente de la pregunta (por ejemplo, en el ejemplo de la pregunta), el error probablemente será sorprendentemente grande, mucho más grande de lo que puede ser aceptable para cualquier aplicación práctica. En cualquier caso, fue una respuesta perspicaz y no debería haber usado ese lenguaje.
Ofir

@Ofir No creo que tu idioma fuera inapropiado. El OP solicitó claramente un cálculo "correcto", no uno que perdería precisión por el hecho de realizarse bajo limitaciones extremas de recursos.
user4815162342

1

Elija K = a big number(p. Ej. K = A - sqrt(A))

A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A-C+B-D); // Avoid overflow.

¿Por qué?

(A-K)*(B-K) = A*B - K*(A+B) + K^2
(C-K)*(D-K) = C*D - K*(C+D) + K^2

=>
(A-K)*(B-K) - (C-K)*(D-K) = A*B - K*(A+B) + K^2 - {C*D - K*(C+D) + K^2}
(A-K)*(B-K) - (C-K)*(D-K) = A*B - C*D - K*(A+B) + K*(C+D) + K^2 - K^2
(A-K)*(B-K) - (C-K)*(D-K) = A*B - C*D - K*(A+B-C-D)

=>
A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A+B-C-D)

=>
A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A-C+B-D)

Nótese que debido a A, B, C y D son números grandes, por lo tanto A-Cy B-Dson números pequeños.


¿Cómo eliges K en la práctica? Además, K * (A-C + BD) aún podría desbordarse.
ylc

@ylc: Elija K = sqrt (A), no 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.
Amir Saniyan

Si elige K = sqrt (A) , entonces (AK) * (BK) podría desbordarse nuevamente.
ylc

@ylc: OK! Lo cambio a A - sqrt(A):)
Amir Saniyan

Entonces K * (A-C + BD) puede desbordarse.
ylc
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.