Quero garantir que uma divisão de números inteiros seja sempre arredondada, se necessário. Existe uma maneira melhor do que isso? Há muitos lançamentos acontecendo. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Quero garantir que uma divisão de números inteiros seja sempre arredondada, se necessário. Existe uma maneira melhor do que isso? Há muitos lançamentos acontecendo. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Respostas:
ATUALIZAÇÃO: Esta pergunta foi o assunto do meu blog em janeiro de 2013 . Obrigado pela ótima pergunta!
Obter aritmética inteira correta é difícil. Como foi demonstrado amplamente até agora, no momento em que você tenta fazer um truque "inteligente", é bem provável que você tenha cometido um erro. E quando uma falha é encontrada, alterar o código para corrigi-la sem considerar se a correção quebra outra coisa não é uma boa técnica de solução de problemas. Até agora, achamos que foram postadas cinco soluções aritméticas inteiras incorretas diferentes para este problema completamente não particularmente difícil.
A maneira correta de abordar problemas aritméticos inteiros - ou seja, a maneira que aumenta a probabilidade de obter a resposta certa da primeira vez - é abordar o problema com cuidado, resolvê-lo um passo de cada vez e usar bons princípios de engenharia para fazer isso. tão.
Comece lendo a especificação para o que você está tentando substituir. A especificação para divisão inteira afirma claramente:
A divisão arredonda o resultado para zero
O resultado é zero ou positivo quando os dois operandos têm o mesmo sinal e zero ou negativo quando os dois operandos têm sinais opostos
Se o operando esquerdo for o menor int representável e o operando direito for –1, ocorrerá um estouro. [...] é definido pela implementação se [uma ArithmeticException] é lançada ou se o estouro não é relatado, com o valor resultante sendo o do operando esquerdo.
Se o valor do operando certo for zero, será lançado um System.DivideByZeroException.
O que queremos é uma função de divisão inteira que calcule o quociente, mas arredonde o resultado sempre para cima , nem sempre para zero .
Então escreva uma especificação para essa função. Nossa função int DivRoundUp(int dividend, int divisor)
deve ter um comportamento definido para todas as entradas possíveis. Esse comportamento indefinido é profundamente preocupante, então vamos eliminá-lo. Diremos que nossa operação tem esta especificação:
operação lança se divisor é zero
A operação será lançada se o dividendo for int.minval e o divisor for -1
se não houver resto - a divisão é 'par' - então o valor de retorno é o quociente integral
Caso contrário, ele retorna o menor número inteiro maior que o quociente, ou seja, sempre arredonda para cima.
Agora temos uma especificação, para que possamos criar um design testável . Suponha que adicionemos um critério de design adicional para que o problema seja resolvido apenas com aritmética inteira, em vez de computar o quociente como um duplo, pois a solução "duplo" foi explicitamente rejeitada na declaração do problema.
Então, o que devemos calcular? Claramente, para atender às nossas especificações e permanecer unicamente na aritmética inteira, precisamos conhecer três fatos. Primeiro, qual foi o quociente inteiro? Segundo, a divisão estava livre do restante? E terceiro, se não, o quociente inteiro foi calculado arredondando para cima ou para baixo?
Agora que temos uma especificação e um design, podemos começar a escrever o código.
public static int DivRoundUp(int dividend, int divisor)
{
if (divisor == 0 ) throw ...
if (divisor == -1 && dividend == Int32.MinValue) throw ...
int roundedTowardsZeroQuotient = dividend / divisor;
bool dividedEvenly = (dividend % divisor) == 0;
if (dividedEvenly)
return roundedTowardsZeroQuotient;
// At this point we know that divisor was not zero
// (because we would have thrown) and we know that
// dividend was not zero (because there would have been no remainder)
// Therefore both are non-zero. Either they are of the same sign,
// or opposite signs. If they're of opposite sign then we rounded
// UP towards zero so we're done. If they're of the same sign then
// we rounded DOWN towards zero, so we need to add one.
bool wasRoundedDown = ((divisor > 0) == (dividend > 0));
if (wasRoundedDown)
return roundedTowardsZeroQuotient + 1;
else
return roundedTowardsZeroQuotient;
}
Isso é inteligente? Não é bonito? Não. Curto? Não. Correto de acordo com a especificação? Acredito que sim, mas ainda não o testei completamente. Parece muito bom embora.
Somos profissionais aqui; use boas práticas de engenharia. Pesquise suas ferramentas, especifique o comportamento desejado, considere os casos de erro primeiro e escreva o código para enfatizar sua correção óbvia. E quando você encontrar um bug, considere se o seu algoritmo é profundamente defeituoso antes de começar aleatoriamente a trocar as direções das comparações e quebrar as coisas que já funcionam.
Todas as respostas aqui até agora parecem bastante complicadas.
Em C # e Java, para dividendo positivo e divisor, você simplesmente precisa:
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)
fornece 1 como resultado. Tomando o tipo certo de divisão, 1+(dividend - 1)/divisor
dá o mesmo resultado que a resposta para dividendos e divisores positivos. Além disso, não há problemas de transbordamento, por mais artificiais que sejam.
Para números inteiros assinados:
int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
div++;
Para números inteiros não assinados:
int div = a / b;
if (a % b != 0)
div++;
A divisão inteira ' /
' é definida para arredondar para zero (7.7.2 da especificação), mas queremos arredondar para cima. Isso significa que as respostas negativas já estão arredondadas corretamente, mas as respostas positivas precisam ser ajustadas.
Respostas positivas diferentes de zero são fáceis de detectar, mas responder zero é um pouco mais difícil, pois pode ser o arredondamento de um valor negativo ou o arredondamento de um valor positivo.
A aposta mais segura é detectar quando a resposta deve ser positiva, verificando se os sinais dos dois números inteiros são idênticos. O operador xor inteiro ' ^
' nos dois valores resultará em um bit de sinal 0 quando for o caso, significando um resultado não negativo, portanto, a verificação (a ^ b) >= 0
determina que o resultado deve ter sido positivo antes do arredondamento. Observe também que, para números inteiros não assinados, todas as respostas são obviamente positivas, portanto, essa verificação pode ser omitida.
A única verificação restante é, então, se ocorreu algum arredondamento, para o qual a % b != 0
fará o trabalho.
Aritmética (número inteiro ou não) não é tão simples quanto parece. Pensar cuidadosamente exigido em todos os momentos.
Além disso, embora minha resposta final talvez não seja tão 'simples' ou 'óbvia' ou talvez até 'rápida' como o ponto flutuante responde, ela tem uma qualidade redentora muito forte para mim; Eu já raciocinei a resposta, então estou realmente certo de que está correto (até que alguém mais inteligente me diga o contrário - um olhar furtivo na direção de Eric -).
Para obter o mesmo sentimento de certeza sobre a resposta de ponto flutuante, que eu teria que fazer mais (e possivelmente mais complicado) pensar sobre se há quaisquer condições em que a precisão de ponto flutuante pode ficar no caminho, e se Math.Ceiling
talvez não algo indesejável nas entradas 'just right'.
Substitua (note que substituí o segundo myInt1
por myInt2
, assumindo que foi isso que você quis dizer):
(int)Math.Ceiling((double)myInt1 / myInt2)
com:
(myInt1 - 1 + myInt2) / myInt2
A única ressalva é que, se myInt1 - 1 + myInt2
exceder o tipo de número inteiro que você está usando, talvez você não consiga o que espera.
Razão pela qual está errado : -1000000 e 3999 devem dar -250, isso dá -249
EDIT:
Considerando que este tem o mesmo erro que a outra solução inteira para myInt1
valores negativos , pode ser mais fácil fazer algo como:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
Isso deve fornecer o resultado correto div
usando apenas operações inteiras.
Razão pela qual está errado : -1 e -5 devem dar 1, isso fornece 0
EDIT (mais uma vez, com sentimento):
O operador de divisão se volta para zero; para resultados negativos, isso é exatamente correto; portanto, apenas resultados não negativos precisam de ajustes. Também considerando que DivRem
apenas faz a /
e a de %
qualquer maneira, vamos pular a chamada (e começar com a comparação fácil para evitar o cálculo do módulo quando não for necessário):
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
Razão pela qual está errado : -1 e 5 devem dar 0, isso fornece 1
(Em minha defesa da última tentativa, eu nunca deveria ter tentado uma resposta fundamentada enquanto minha mente me dizia que eu estava duas horas atrasada para dormir)
Oportunidade perfeita para usar um método de extensão:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
Isso torna seu código super legível também:
int result = myInt.DivideByAndRoundUp(4);
Você poderia escrever um ajudante.
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
Você pode usar algo como o seguinte.
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
Algumas das respostas acima usam carros alegóricos, isso é ineficiente e realmente não é necessário. Para entradas não assinadas, essa é uma resposta eficiente para int1 / int2:
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
Para entradas assinadas, isso não estará correto
O problema com todas as soluções aqui é que elas precisam de uma conversão ou têm um problema numérico. Moldar para flutuar ou dobrar é sempre uma opção, mas podemos fazer melhor.
Quando você usa o código da resposta de @jerryjvl
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
há um erro de arredondamento. 1/5 seria arredondado, porque 1% 5! = 0. Mas isso está errado, porque o arredondamento só ocorrerá se você substituir o 1 por um 3, portanto o resultado é 0,6. Precisamos encontrar uma maneira de arredondar quando o cálculo nos fornecer um valor maior ou igual a 0,5. O resultado do operador módulo no exemplo superior tem um intervalo de 0 a myInt2-1. O arredondamento ocorrerá apenas se o restante for maior que 50% do divisor. Portanto, o código ajustado fica assim:
int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
div++;
É claro que também temos um problema de arredondamento no myInt2 / 2, mas esse resultado fornecerá uma solução de arredondamento melhor do que os outros neste site.