Como evitar o estouro em expr. A * B - C * D

161

Preciso calcular uma expressão que se pareça com:, A*B - C*Donde estão seus tipos: signed long long int A, B, C, D; Cada número pode ser muito grande (sem exceder o seu tipo). Embora A*Bpossa causar estouro, ao mesmo tempo, a expressão A*B - C*Dpode ser muito pequena. Como posso calcular corretamente?

Por exemplo:, MAX * MAX - (MAX - 1) * (MAX + 1) == 1onde MAX = LLONG_MAX - ne n - algum número natural.

NGix
fonte
17
Quão importante é a precisão?
Anirudh Ramanathan
1
@Thulhu, ótima pergunta. Ele poderia tentar criar uma função equivalente usando um número menor, dividindo todos eles por 10 ou algo assim, multiplicando o resultado.
Chris
4
Os Vars A, B, C, D são assinados. Isso implica que A - Cpode transbordar. É uma questão a considerar ou você sabe que isso não vai acontecer com seus dados?
22420 William Morris
2
@MooingDuck mas você pode verificar com antecedência se a operação vai transbordar stackoverflow.com/a/3224630/158285
bradgonesurfing
1
@ Chris: Não, estou dizendo que não há uma maneira portátil de verificar se ocorreu um estouro assinado. (Brad é correto que você pode portably detectar que vai acontecer). O uso de montagem embutida é uma das muitas maneiras não portáteis de verificar.
Mooing Duck

Respostas:

120

Isso parece muito trivial, eu acho. Mas A*Bé o único que pode transbordar.

Você pode fazer o seguinte, sem perder a precisão

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)

Essa decomposição pode ser feita ainda mais .
Como o @Gian apontou, talvez seja necessário tomar cuidado durante a operação de subtração se o tipo não tiver assinatura por muito tempo.


Por exemplo, com o caso em questão, são necessárias apenas uma iteração,

 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
Anirudh Ramanathan
fonte
4
@Caleb, basta aplicar o mesmo algoritmo paraC*D
Chris
2
Eu acho que você deveria explicar o que E representa.
Caleb
7
Tanto long long e double são 64 bits. Como o dobro tem que alocar alguns bits para o expoente, ele possui uma faixa menor de valores possíveis sem perda de precisão.
Jim Garrison
3
@Cthulhu - parece-me que isso só funcionaria se todo o número fosse muito grande ... por exemplo, você ainda estaria sobrecarregado com {A, B, C, D} = {MAX, MAX, MAX, 2}. O OP diz "Cada número pode ser muito grande", mas não está claro na declaração do problema que cada número deve ser realmente grande.
Kevin K
4
E se algum A,B,C,Ddeles for negativo? Não será Eou Fserá ainda maior então?
Supr
68

A solução mais simples e mais geral é usar uma representação que não pode transbordar, usando uma biblioteca inteira longa (por exemplo, http://gmplib.org/ ) ou representando usando uma estrutura ou matriz e implementando um tipo de multiplicação longa ( isto é, separar cada número em duas metades de 32 bits e realizar a multiplicação conforme abaixo:

(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

Supondo que o resultado final se encaixe em 64 bits, você realmente não precisa da maioria dos bits de R3 e nenhum de R4

Ofir
fonte
8
O cálculo acima não é realmente tão complicado quanto parece, é realmente uma multiplicação longa simples na base 2 ^ 32, e o código em C deve parecer mais simples. Além disso, será uma boa ideia criar funções genéricas para fazer esse trabalho no seu programa.
Ofir
46

Observe que isso não é padrão, uma vez que se baseia no estouro de sinalização wrap-around. (O GCC possui sinalizadores de compilador que permitem isso.)

Mas se você fizer todos os cálculos long long, o resultado da aplicação direta da fórmula:
(A * B - C * D)será preciso enquanto o resultado correto se encaixar em a long long.


Aqui está uma solução alternativa que depende apenas do comportamento definido pela implementação de converter um número inteiro não assinado em número inteiro assinado. Mas isso pode funcionar em quase todos os sistemas atualmente.

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

Isso lança as entradas para as unsigned long longquais o comportamento de estouro é garantido como padrão pelo padrão. A conversão de volta para um número inteiro assinado no final é a parte definida pela implementação, mas funcionará em quase todos os ambientes hoje.


Se você precisar de uma solução mais pedante, acho que você deve usar "aritmética longa"

RiaD
fonte
+1 Você é o único a perceber isso. A única parte complicada é configurar o compilador para realizar transbordamento e verificar se o resultado correto realmente se encaixa em um long long.
Mysticial 5/11/12
2
Mesmo a versão ingênua, sem truques, fará a coisa certa na maioria das implementações; não é garantido pelo padrão, mas você precisaria encontrar uma máquina com complemento de 1 ou algum outro dispositivo bastante estranho para fazê-lo falhar.
Hbbs #
1
Eu acho que essa é uma resposta importante. Concordo que pode não ser uma programação correta assumir o comportamento específico da implementação, mas todo engenheiro deve entender a aritmética do módulo e como obter os sinalizadores do compilador certos para garantir um comportamento consistente se o desempenho for essencial. Os engenheiros de DSP contam com esse comportamento para implementações de filtro de ponto fixo, para as quais a resposta aceita terá desempenho inaceitável.
Peter M
18

Isso deve funcionar (eu acho):

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

Aqui está minha derivação:

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)
paquetp
fonte
1
Obrigado @bradgonesurfing - você poderia fornecer uma contribuição dessas? Eu atualizei a minha resposta, executa-lo e ele funciona (bd e ca são 0) ...
paquetp
1
Hummm. Agora penso nisso, talvez não. Caso degenerado com d = 1 e a = 1 eb = maxint ec = maxint ainda funciona. Cool :)
bradgonesurfing
1
@paquetp: a = 1, b = 0x7fffffffffffffff, c = -0x7fffffffffffffff, d = 1 (a nota c é negativa). Inteligente, porém, tenho certeza de que seu código lida com todos os números positivos corretamente.
Mooing Duck
3
@MooingDuck, mas a resposta final para o seu conjunto também foi estourada, por isso não é uma configuração válida. Só funciona se cada lado tiver o mesmo sinal, de modo que a subtração resultante esteja dentro do alcance.
Bradgonesurfing 5/11/12
1
Há algo de estranho no StackOverflow quando essa resposta, que é a mais simples e a melhor, tem uma pontuação tão baixa em comparação com a resposta mais pontuada.
Bradgonesurfing 06/11/12
9

Você pode considerar o cálculo do maior fator comum para todos os seus valores e depois dividi-los por esse fator antes de executar suas operações aritméticas e depois multiplicar novamente. Isso pressupõe que tal fator um existe, no entanto (por exemplo, se A, B, Ce Dacontecer de ser relativamente primos, eles não têm um fator comum).

Da mesma forma, você pode considerar trabalhar em escalas de log, mas isso será um pouco assustador, sujeito à precisão numérica.

Gian
fonte
1
O logaritmo parece bom se long doubleestiver disponível. Nesse caso, um nível aceitável de precisão pode ser alcançado (e o resultado pode ser arredondado).
9

Se o resultado se encaixa em um longo e longo int, então a expressão A * BC * D está correta, pois executa o mod aritmético 2 ^ 64 e fornecerá o resultado correto. O problema é saber se o resultado se encaixa em um int muito longo. Para detectar isso, você pode usar o seguinte truque usando duplas:

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

O problema dessa abordagem é que você é limitado pela precisão da mantissa das dobras (54 bits?), Portanto, você precisa limitar os produtos A * B e C * D a 63 + 54 bits (ou provavelmente um pouco menos).

Esteban Crespi
fonte
Este é o exemplo mais prático. Limpe e dê a resposta certa (ou lança uma exceção quando as entradas estão ruins).
precisa saber é o seguinte
1
Agradável e elegante! Você não caiu na armadilha pela qual os outros caíram. Só mais uma coisa: aposto que existem alguns exemplos em que o cálculo duplo está abaixo de MAX_LLONG apenas devido a erros de arredondamento. Meu instinto matemático diz que você deve calcular a diferença entre o resultado duplo e o longo e comparar com MAX_LLONG / 2 ou algo assim. Essa diferença são os erros de arredondamento do cálculo duplo e o excesso de excesso e normalmente devem ser relativamente baixos, mas no caso mencionado, será grande. Mas agora estou com preguiça de descobrir com certeza. :-)
Hans-Peter Störr 12/12/12
9
E = max(A,B,C,D)
A1 = A -E;
B1 = B -E;
C1 = C -E;
D1 = D -E;

então

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

Você pode escrever cada número em uma matriz, cada elemento sendo um dígito e fazer os cálculos como polinômios . Pegue o polinômio resultante, que é uma matriz, e calcule o resultado multiplicando cada elemento da matriz por 10 à potência da posição na matriz (a primeira posição sendo a maior e a última sendo zero).

O número 123pode ser expresso como:

123 = 100 * 1 + 10 * 2 + 3

para o qual você acabou de criar uma matriz [1 2 3].

Você faz isso para todos os números A, B, C e D e os multiplica como polinômios. Depois de obter o polinômio resultante, você apenas reconstrói o número a partir dele.

Mihai
fonte
2
não sei o que é isso, mas vou ter que encontrar. colocar :) . esta é uma solução do topo da minha cabeça, enquanto eu estou fazendo compras com a minha namorada :)
Mihai
você está implementando bignums em uma matriz base10. GMP é uma biblioteca de qualidade bignum que usa a base 4294967296. MUITO mais rápido. Não há voto negativo, porque a resposta é correta e útil.
Mooing Duck
obrigado :) . é útil saber que essa é uma maneira de fazê-lo, mas existem maneiras melhores, então não faça assim. pelo menos não nesta situação :) #
315 Mihai
de qualquer maneira ... usando esta solução, você poderia computar um número muito maior do que qualquer tipo primitivo poderia usar negrito (como números de 100 dígitos) e manter o resultado como uma matriz. isso merece uma votez-se: p
Mihai
Não tenho certeza de que receba um voto positivo, pois esse método (embora eficaz e relativamente fácil de entender) consome muita memória e é lento.
Mooing Duck
6

Enquanto um signed long long intnão aguentar A*B, dois deles o farão. Assim, A*Bpoderia ser decomposto em termos de árvore de diferentes expoentes, qualquer um deles adequado 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;

O mesmo para C*D.

Seguindo da maneira correta, a sub-ação poderia ser feita para todos os pares de, AB_ie da CD_imesma forma, usando um bit de transporte adicional (com precisão um número inteiro de 1 bit) para cada um. Então, se dissermos E = A * BC * D, você terá 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 transferindo a metade superior de E_10para E_20(mude 32 e adicione e depois apague a metade superior E_10).

Agora você pode se livrar do bit de transporte E_11, adicionando-o com o sinal correto (obtido da parte de não transporte) a E_20. Se isso disparar um estouro, o resultado também não seria adequado.

E_10agora tem 'espaço' suficiente para retirar a metade superior E_00 (mudar, adicionar, apagar) e o bit de transporte E_01.

E_10agora pode ser maior novamente, então repetimos a transferência para E_20.

Neste ponto, E_20deve se tornar zero, caso contrário, o resultado não será adequado. A metade superior também E_10está vazia como resultado da transferência.

O passo final é transferir a metade inferior E_20para E_10novamente.

Se a expectativa que E=A*B+C*Dcaberia nos signed long long intporões, agora temos

E_20=0
E_10=0
E_00=E
dronus
fonte
1
Esta é realmente a fórmula simplificada que você obteria se usasse a fórmula de multiplicação de Ofir e removesse todo resultado temporário inútil.
Dronus
3

Se você sabe que o resultado final é representável no seu tipo inteiro, você pode executar esse cálculo rapidamente usando o código abaixo. Como o padrão C especifica que a aritmética não assinada é aritmética modular e não transborda, é possível usar um tipo não assinado para executar o cálculo.

O código a seguir assume que existe um tipo não assinado da mesma largura e que o tipo assinado usa todos os padrões de bits para representar valores (sem representações de trap, o mínimo do tipo assinado é o negativo da metade do módulo do tipo não assinado). Se isso não ocorrer em uma implementação C, ajustes simples poderão ser feitos na rotina ConvertToSigned para isso.

O seguinte usa signed chare unsigned charpara demonstrar o código. Para sua implementação, altere a definição de Signedpara typedef signed long long int Signed;e a definição de Unsignedpara 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;
}
Eric Postpischil
fonte
2

Você pode tentar dividir a equação em componentes menores que não transbordam.

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

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

    where K = B - N
          J = D - M

Se os componentes ainda estiverem transbordando, você poderá dividi-los em componentes menores recursivamente e recombinar.

bradgonesurfing
fonte
Isso pode ou não estar correto, mas é definitivamente confuso. Você define Ke J, por que não Ne M. Além disso, acho que você está dividindo a equação em pedaços maiores . Como o passo 3 é o mesmo que a pergunta do OP, exceto mais complicado (AK-CJ)->(AB-CD)
Mooing Duck
N não é simplificado de nada. É apenas um número subtraído de A para torná-lo menor. Na verdade, é uma solução semelhante, mas inferior ao paquetp. Aqui estou usando subtração em vez de divisão inteira para torná-lo menor.
Bradgonesurfing 5/11/12
2

Talvez eu não tenha abordado todos os casos extremos, nem testei rigorosamente isso, mas isso implementa uma técnica que me lembro de usar nos anos 80 ao tentar fazer cálculos inteiros de 32 bits em uma CPU de 16 bits. Basicamente, você divide os 32 bits em duas unidades de 16 bits e trabalha com eles separadamente.

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());

  }

}

Impressões:

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

o que me parece que está funcionando.

Aposto que perdi algumas das sutilezas, como observar o excesso de sinal, etc., mas acho que a essência está lá.

OldCurmudgeon
fonte
1
Eu acho que é uma implementação do que o @Ofir sugeriu.
precisa saber é o seguinte
2

Por uma questão de completude, como ninguém o mencionou, alguns compiladores (por exemplo, GCC) realmente fornecem um número inteiro de 128 bits hoje em dia.

Assim, uma solução fácil pode ser:

(long long)((__int128)A * B - (__int128)C * D)
i Código 4 Alimentos
fonte
1

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Nem B/Cnem D/Apode transbordar, por isso, calcular (B/C-D/A)em primeiro lugar. Como o resultado final não transbordará de acordo com sua definição, você pode realizar com segurança as multiplicações restantes e calcular (B/C-D/A)*A*Cqual é o resultado necessário.

Observe que, se sua entrada também pode ser extremamente pequena , a B/Cou D/Apode estourar. Se possível, manipulações mais complexas podem ser necessárias de acordo com a inspeção de entrada.

SomeWittyUsername
fonte
2
Isso não vai funcionar como divisão inteira perde informação (a fração de resultado)
Ofir
@Ofir está correto, no entanto, você não pode comer o bolo e deixá-lo intocado. Você precisa pagar com precisão ou usando recursos adicionais (como sugerido na sua resposta). Minha resposta é de natureza matemática, enquanto a sua é orientada por computador. Cada um pode estar correto, dependendo das circunstâncias.
SomeWittyUsername
2
Você está correto - eu deveria ter dito isso como - não dará um resultado exato e não funcionará, pois a matemática está correta. No entanto, observe que nos casos que provavelmente interessam ao remetente da pergunta (por exemplo, no exemplo da pergunta), o erro provavelmente será surpreendentemente grande - muito maior do que o aceitável para qualquer aplicação prática. De qualquer forma - foi uma resposta perspicaz e eu não deveria ter usado esse idioma.
Ofir
@Ofir Não acho que seu idioma seja inadequado. O OP solicitou claramente um cálculo "correto", não aquele que perderia a precisão por causa de ser executado sob restrições extremas de recursos.
user4815162342
1

Escolha K = a big number(por exemplo 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)

Note que, como A, B, C e D são grandes números, portanto, A-Ce B-Dsão pequenos números.

Amir Saniyan
fonte
Como você escolhe K na prática? Além disso, o K * (A-C + BD) ainda pode transbordar.
7402 jul
@ylc: Escolha K = sqrt (A), não que A-C+B-Dseja um número pequeno. Como A, B, C e D são grandes números, portanto, AC é pequeno.
Amir Saniyan #
Se você escolher K = sqrt (A) , então (AK) * (BK) poderá estourar novamente.
Il /
@ylc: OK! Eu mudar para A - sqrt(A):)
Amir Saniyan
Então K * (A-C + BD) pode transbordar.
7114 ylc