Tenho 3 inteiros com sinal muito grandes.
long x = long.MaxValue;
long y = long.MaxValue - 1;
long z = long.MaxValue - 2;
Quero calcular sua média truncada. O valor médio esperado é long.MaxValue - 1
, que é 9223372036854775806
.
É impossível calculá-lo como:
long avg = (x + y + z) / 3; // 3074457345618258600
Nota: eu li todas aquelas questões sobre a média de 2 números, mas não vejo como essa técnica pode ser aplicada à média de 3 números.
Seria muito fácil com o uso de BigInteger
, mas vamos assumir que não posso usá-lo.
BigInteger bx = new BigInteger(x);
BigInteger by = new BigInteger(y);
BigInteger bz = new BigInteger(z);
BigInteger bavg = (bx + by + bz) / 3; // 9223372036854775806
Se eu converter para double
, é claro, perco a precisão:
double dx = x;
double dy = y;
double dz = z;
double davg = (dx + dy + dz) / 3; // 9223372036854780000
Se eu converter para decimal
, funcionará, mas também vamos supor que não posso usá-lo.
decimal mx = x;
decimal my = y;
decimal mz = z;
decimal mavg = (mx + my + mz) / 3; // 9223372036854775806
Pergunta: Existe uma maneira de calcular a média truncada de 3 inteiros muito grandes apenas com o uso de long
tipo? Não considere essa pergunta como específica do C #, apenas é mais fácil para mim fornecer exemplos em C #.
fonte
long.MinValue
elong.MaxValue
entre os valores.BigInteger
oudecimal
é excluído, ou é apenas por uma questão de fazer este disco?Respostas:
Este código funcionará, mas não é tão bonito.
Ele primeiro divide todos os três valores (reduz os valores, então você 'perde' o restante) e, em seguida, divide o restante:
Observe que o exemplo acima nem sempre funciona corretamente quando tem um ou mais valores negativos.
Conforme discutido com Ulugbek, como o número de comentários está explodindo abaixo, aqui está a MELHOR solução atual para valores positivos e negativos.
Graças às respostas e comentários de Ulugbek Umirov , James S , KevinZ , Marc van Leeuwen , gnasher729 , esta é a solução atual:
fonte
(x + y + z) / 3 = x / 3 + y / 3 + z / 3
,.f(1,1,2) == 1
enquantof(-2,-2,8) == 2
(x+y)/3
que é muito.NB - O Patrick já deu uma ótima resposta . Expandindo isso, você poderia fazer uma versão genérica para qualquer número de inteiros, como:
fonte
long
, mas para tipos menores, observe que a segunda soma pode estourar.Patrick Hofman postou uma ótima solução . Mas, se necessário, ainda pode ser implementado de várias outras maneiras. Usando o algoritmo aqui , tenho outra solução. Se implementado com cuidado, pode ser mais rápido do que as várias divisões em sistemas com divisores de hardware lentos. Ele pode ser otimizado ainda mais usando a técnica de divisão por constantes , para o deleite do hacker
Em C / C ++ em plataformas de 64 bits é muito mais fácil com
__int128
fonte
x=y/3
via sem sinalx=y>>2; x+=x>>2; x+=x>>4; x+=x>>8; x+=x>>16; x+=x>>32;
. O resultado será muito próximo de x e pode ser tornado preciso calculandodelta=y-x-x-x;
e usando os ajustesx
necessários.Você pode calcular a média dos números com base nas diferenças entre os números, em vez de usar a soma.
Digamos que x é o máximo, y é a mediana, z é o mínimo (como você fez). Vamos chamá-los de máx., Mediana e mín.
Verificador condicional adicionado de acordo com o comentário de @ UlugbekUmirov:
fonte
(double)(2 / 3)
igual a 0,0?Como C usa a divisão com base em vez da divisão euclidiana, pode ser mais fácil calcular uma média corretamente arredondada de três valores sem sinal do que de três valores com sinal. Basta adicionar 0x8000000000000000UL a cada número antes
Int64
de calcular a média não sinalizada , subtraí-la após obter o resultado e usar um elenco não verificado de volta para obter uma média sinalizada.Para calcular a média sem sinal, calcule a soma dos 32 bits principais dos três valores. Em seguida, calcule a soma dos 32 bits inferiores dos três valores, mais a soma de cima, mais um [o mais um produzirá um resultado arredondado]. A média será 0x55555555 vezes a primeira soma, mais um terço da segunda.
O desempenho em processadores de 32 bits pode ser aprimorado produzindo três valores de "soma", cada um dos quais com 32 bits de comprimento, de modo que o resultado final seja
((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3
; possivelmente, pode ser aprimorado substituindosumL/3
com((sumL * 0x55555556UL) >> 32)
, embora o último dependa do otimizador JIT [ele pode saber como substituir uma divisão por 3 por uma multiplicação, e seu código pode realmente ser mais eficiente do que uma operação de multiplicação explícita].fonte
Remendando a solução de Patrick Hofman com a correção do supercat , eu lhe dou o seguinte:
E o caso do elemento N:
Isso sempre fornece o piso () da média e elimina todos os casos extremos possíveis.
fonte
Você poderia usar o fato de que pode escrever cada um dos números como
y = ax + b
, ondex
é uma constante. Cada uma
seriay / x
(a parte inteira dessa divisão). Cada b seriay % x
(o resto / módulo dessa divisão). Se você escolher essa constante de forma inteligente, por exemplo, escolhendo a raiz quadrada do número máximo como uma constante, você pode obter a média dosx
números sem ter problemas com estouro.A média de uma lista arbitrária de números pode ser encontrada encontrando:
onde
%
denota módulo e/
denota a parte 'inteira' da divisão.O programa seria algo como:
fonte
Se você sabe que tem N valores, pode simplesmente dividir cada valor por N e somá-los?
fonte
Eu também tentei e encontrei uma solução mais rápida (embora apenas por um fator de cerca de 3/4). Ele usa uma única divisão
onde
smallDiv3
é a divisão por 3 usando multiplicação e trabalhando apenas para pequenos argumentosAqui está todo o código, incluindo um teste e um benchmark, os resultados não são tão impressionantes.
fonte
Esta função calcula o resultado em duas divisões. Deve generalizar bem para outros divisores e tamanhos de palavras.
Ele funciona calculando o resultado da adição de palavras duplas e, em seguida, calculando a divisão.
fonte
Matemática
Código
fonte
{1,2,3}
resposta é2
, mas seu código retornará1
.double
, uma vez que vamos perder a precisão nesse caso.Experimente isto:
fonte