Tengo 3 enteros con signo muy grandes.
long x = long.MaxValue;
long y = long.MaxValue - 1;
long z = long.MaxValue - 2;
Quiero calcular su promedio truncado. El valor promedio esperado es long.MaxValue - 1
, que es 9223372036854775806
.
Es imposible calcularlo como:
long avg = (x + y + z) / 3; // 3074457345618258600
Nota: Leí todas esas preguntas sobre el promedio de 2 números, pero no veo cómo se puede aplicar esa técnica al promedio de 3 números.
Sería muy fácil con el uso de BigInteger
, pero supongamos que no puedo usarlo.
BigInteger bx = new BigInteger(x);
BigInteger by = new BigInteger(y);
BigInteger bz = new BigInteger(z);
BigInteger bavg = (bx + by + bz) / 3; // 9223372036854775806
Si me convierto a double
, entonces, por supuesto, pierdo precisión:
double dx = x;
double dy = y;
double dz = z;
double davg = (dx + dy + dz) / 3; // 9223372036854780000
Si convierto a decimal
, funciona, pero también supongamos que no puedo usarlo.
decimal mx = x;
decimal my = y;
decimal mz = z;
decimal mavg = (mx + my + mz) / 3; // 9223372036854775806
Pregunta: ¿Hay alguna manera de calcular el promedio truncado de 3 enteros muy grandes solo con el uso de long
tipo? No considere esa pregunta como específica de C #, solo que es más fácil para mí proporcionar ejemplos en C #.
fuente
long.MinValue
ylong.MaxValue
entre valores.BigInteger
odecimal
se excluye, o es solo por hacer esto difícil?Respuestas:
Este código funcionará, pero no es tan bonito.
Primero divide los tres valores (reduce los valores, por lo que 'pierde' el resto) y luego divide el resto:
Tenga en cuenta que el ejemplo anterior no siempre funciona correctamente cuando tiene uno o más valores negativos.
Como se discutió con Ulugbek, dado que la cantidad de comentarios está aumentando a continuación, aquí está la MEJOR solución actual para valores positivos y negativos.
Gracias a las respuestas y comentarios de Ulugbek Umirov , James S , KevinZ , Marc van Leeuwen , gnasher729 esta es la solución actual:
fuente
(x + y + z) / 3 = x / 3 + y / 3 + z / 3
.f(1,1,2) == 1
whilef(-2,-2,8) == 2
(x+y)/3
cuál es demasiado.NB: Patrick ya ha dado una gran respuesta . Ampliando esto, podría hacer una versión genérica para cualquier número de enteros como este:
fuente
long
, pero para tipos más pequeños, tenga en cuenta que la segunda suma puede desbordarse.Patrick Hofman ha publicado una gran solución . Pero si es necesario, aún se puede implementar de varias otras formas. Usando el algoritmo aquí tengo otra solución. Si se implementa con cuidado, puede ser más rápido que las divisiones múltiples en sistemas con divisores de hardware lentos. Se puede optimizar aún más utilizando la técnica de dividir por constantes del deleite de los piratas informáticos.
En C / C ++ en plataformas de 64 bits es mucho más fácil con
__int128
fuente
x=y/3
vía sin firmarx=y>>2; x+=x>>2; x+=x>>4; x+=x>>8; x+=x>>16; x+=x>>32;
. El resultado será muy cercano ax, y se puede precisar calculandodelta=y-x-x-x;
y ajustandox
según sea necesario.Puede calcular la media de los números basándose en las diferencias entre los números en lugar de usar la suma.
Digamos que x es el máximo, y es la mediana, z es el mínimo (como tienes). Los llamaremos max, median y min.
Verificador condicional agregado según el comentario de @ UlugbekUmirov:
fuente
(double)(2 / 3)
igual a 0.0?Debido a que C usa la división en suelo en lugar de la división euclidiana, puede ser más fácil calcular un promedio correctamente redondeado de tres valores sin signo que con tres valores con signo. Simplemente agregue 0x8000000000000000UL a cada número antes de tomar el promedio sin firmar, réstelo después de tomar el resultado y use una conversión sin marcar para
Int64
obtener un promedio con signo.Para calcular el promedio sin signo, calcule la suma de los 32 bits superiores de los tres valores. Luego calcule la suma de los 32 bits inferiores de los tres valores, más la suma de arriba, más uno [el más uno es para obtener un resultado redondeado]. El promedio será 0x55555555 veces la primera suma, más un tercio de la segunda.
El rendimiento en procesadores de 32 bits podría mejorarse produciendo tres valores de "suma", cada uno de los cuales tiene una longitud de 32 bits, de modo que el resultado final sea
((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3
; posiblemente podría mejorarse aún más reemplazandosumL/3
con((sumL * 0x55555556UL) >> 32)
, aunque este último dependería del optimizador JIT [podría saber cómo reemplazar una división por 3 con una multiplicación, y su código podría ser más eficiente que una operación de multiplicación explícita].fuente
Parcheando la solución de Patrick Hofman con la corrección de supercat , les doy lo siguiente:
Y el caso del elemento N:
Esto siempre da el piso () de la media y elimina todos los casos de borde posibles.
fuente
Podrías usar el hecho de que puedes escribir cada uno de los números como
y = ax + b
, dondex
es una constante. Cada unoa
seríay / x
(la parte entera de esa división). Cada b seríay % x
(el resto / módulo de esa división). Si elige esta constante de manera inteligente, por ejemplo, eligiendo la raíz cuadrada del número máximo como constante, puede obtener el promedio dex
números sin tener problemas de desbordamiento.El promedio de una lista arbitraria de números se puede encontrar encontrando:
donde
%
denota módulo y/
denota la parte "completa" de la división.El programa se vería así:
fuente
Si sabe que tiene N valores, ¿puede simplemente dividir cada valor entre N y sumarlos?
fuente
También lo probé y se me ocurrió una solución más rápida (aunque solo por un factor de aproximadamente 3/4). Utiliza una sola división
donde
smallDiv3
es la división por 3 usando la multiplicación y trabajando solo para pequeños argumentosAquí está el código completo, incluida una prueba y un punto de referencia, los resultados no son tan impresionantes.
fuente
Esta función calcula el resultado en dos divisiones. Debería generalizarse bien a otros divisores y tamaños de palabras.
Funciona calculando el resultado de la suma de dos palabras y luego calculando la división.
fuente
Matemáticas
Código
fuente
{1,2,3}
la respuesta es2
, pero su código regresará1
.double
, ya que vamos a perder precisión en tal caso.Prueba esto:
fuente