Preciso calcular uma expressão que se pareça com:,
A*B - C*D
onde 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*B
possa causar estouro, ao mesmo tempo, a expressão A*B - C*D
pode ser muito pequena. Como posso calcular corretamente?
Por exemplo:, MAX * MAX - (MAX - 1) * (MAX + 1) == 1
onde MAX = LLONG_MAX - n
e n - algum número natural.
c++
c
integer-overflow
NGix
fonte
fonte
A - C
pode transbordar. É uma questão a considerar ou você sabe que isso não vai acontecer com seus dados?Respostas:
Isso parece muito trivial, eu acho. Mas
A*B
é o único que pode transbordar.Você pode fazer o seguinte, sem perder a precisão
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,
fonte
C*D
A,B,C,D
deles for negativo? Não seráE
ouF
será ainda maior então?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:
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
fonte
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 along 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.
Isso lança as entradas para as
unsigned long long
quais 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"
fonte
long long
.Isso deve funcionar (eu acho):
Aqui está minha derivação:
fonte
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
,C
eD
acontecer 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.
fonte
long double
estiver disponível. Nesse caso, um nível aceitável de precisão pode ser alcançado (e o resultado pode ser arredondado).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:
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).
fonte
então
fonte
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
123
pode ser expresso como: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.
fonte
Enquanto um
signed long long int
não aguentarA*B
, dois deles o farão. Assim,A*B
poderia ser decomposto em termos de árvore de diferentes expoentes, qualquer um deles adequadosigned long long int
.O mesmo para
C*D
.Seguindo da maneira correta, a sub-ação poderia ser feita para todos os pares de,
AB_i
e daCD_i
mesma 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:Continuamos transferindo a metade superior de
E_10
paraE_20
(mude 32 e adicione e depois apague a metade superiorE_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) aE_20
. Se isso disparar um estouro, o resultado também não seria adequado.E_10
agora tem 'espaço' suficiente para retirar a metade superiorE_00
(mudar, adicionar, apagar) e o bit de transporteE_01
.E_10
agora pode ser maior novamente, então repetimos a transferência paraE_20
.Neste ponto,
E_20
deve se tornar zero, caso contrário, o resultado não será adequado. A metade superior tambémE_10
está vazia como resultado da transferência.O passo final é transferir a metade inferior
E_20
paraE_10
novamente.Se a expectativa que
E=A*B+C*D
caberia nossigned long long int
porões, agora temosfonte
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 char
eunsigned char
para demonstrar o código. Para sua implementação, altere a definição deSigned
paratypedef signed long long int Signed;
e a definição deUnsigned
paratypedef unsigned long long int Unsigned;
.fonte
Você pode tentar dividir a equação em componentes menores que não transbordam.
Se os componentes ainda estiverem transbordando, você poderá dividi-los em componentes menores recursivamente e recombinar.
fonte
K
eJ
, por que nãoN
eM
. 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)
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.
Impressões:
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á.
fonte
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:
fonte
AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C
. NemB/C
nemD/A
pode 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*C
qual é o resultado necessário.Observe que, se sua entrada também pode ser extremamente pequena , a
B/C
ouD/A
pode estourar. Se possível, manipulações mais complexas podem ser necessárias de acordo com a inspeção de entrada.fonte
Escolha
K = a big number
(por exemploK = A - sqrt(A)
)Por quê?
Note que, como A, B, C e D são grandes números, portanto,
A-C
eB-D
são pequenos números.fonte
A-C+B-D
seja um número pequeno. Como A, B, C e D são grandes números, portanto, AC é pequeno.A - sqrt(A)
:)