Я хочу убедиться, что деление целых чисел всегда округляется при необходимости. Есть ли лучший способ, чем этот? Происходит много кастингов. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Я хочу убедиться, что деление целых чисел всегда округляется при необходимости. Есть ли лучший способ, чем этот? Происходит много кастингов. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Ответы:
ОБНОВЛЕНИЕ: Этот вопрос был темой моего блога в январе 2013 года . Спасибо за отличный вопрос!
Получить правильную целочисленную арифметику сложно. Как было наглядно продемонстрировано, в тот момент, когда вы пытаетесь сделать «умный» трюк, вероятность того, что вы допустили ошибку, высока. И когда обнаружен недостаток, изменение кода для исправления недостатка без учета того, нарушает ли исправление что-либо еще , не является хорошим методом решения проблем. До сих пор у нас было пять различных неправильных целочисленных арифметических решений этой совершенно не особо сложной проблемы.
Правильный подход к целочисленным арифметическим задачам, то есть способ повысить вероятность получения правильного ответа в первый раз, - это подходить к проблеме осторожно, решать ее шаг за шагом и использовать хорошие инженерные принципы при выполнении так.
Начните с чтения спецификации того, что вы пытаетесь заменить. Спецификация для целочисленного деления четко гласит:
Деление округляет результат до нуля
Результат равен нулю или положителен, если два операнда имеют одинаковый знак, и равен нулю или отрицанию, если два операнда имеют противоположные знаки.
Если левый операнд является наименьшим представимым типом int, а правый операнд равен –1, происходит переполнение. [...] это определяется реализацией относительно того, выбрасывается ли [ArithmeticException] или переполнение не сообщается, а полученное значение равно значению левого операнда.
Если значение правого операнда равно нулю, генерируется исключение System.DivideByZeroException.
Нам нужна целочисленная функция деления, которая вычисляет частное, но округляет результат всегда вверх , а не всегда к нулю .
Так что напишите спецификацию для этой функции. Наша функция int DivRoundUp(int dividend, int divisor)
должна иметь поведение, определенное для каждого возможного ввода. Это неопределенное поведение вызывает глубокую тревогу, поэтому давайте устраним его. Мы скажем, что наша операция имеет эту спецификацию:
операция выбрасывает, если делитель равен нулю
Операция выдает, если divind is int.minval и divisor равен -1
если нет остатка - деление 'четное' - тогда возвращаемое значение является целым частным
В противном случае он возвращает наименьшее целое число, большее, чем частное, то есть оно всегда округляется.
Теперь у нас есть спецификация, поэтому мы знаем, что можем разработать тестируемый дизайн . Предположим, что мы добавили дополнительный критерий проектирования, согласно которому задача должна решаться исключительно с помощью целочисленной арифметики, а не вычисления коэффициента как двойного, поскольку «двойное» решение было явно отвергнуто в постановке задачи.
Так что мы должны вычислять? Понятно, что для соответствия нашей спецификации, оставаясь исключительно в целочисленной арифметике, нам нужно знать три факта. Во-первых, каково целое отношение? Во-вторых, было ли разделение свободным от остатка? И в-третьих, если нет, вычислялся ли целочисленный коэффициент округлением в большую или меньшую сторону?
Теперь, когда у нас есть спецификация и дизайн, мы можем начать писать код.
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;
}
Это умно? Нет. Красиво? Нет. Коротко? Правильно ли в соответствии со спецификацией? Я верю в это, но я не до конца проверил это. Это выглядит довольно хорошо, хотя.
Мы профессионалы здесь; использовать хорошие инженерные практики. Изучите свои инструменты, укажите желаемое поведение, сначала рассмотрите случаи ошибок и напишите код, чтобы подчеркнуть его очевидную правильность. И когда вы обнаружите ошибку, подумайте, не является ли ваш алгоритм ошибочным для начала, прежде чем вы просто начнете случайным образом менять местами сравнения и разбивать вещи, которые уже работают.
Все ответы здесь пока кажутся слишком сложными.
В C # и Java для положительных дивидендов и делителей вам просто нужно сделать:
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)
дает 1 как результат. Принятие правильного вида деления, 1+(dividend - 1)/divisor
дает тот же результат, что и ответ за положительный дивиденд и делитель. Также проблем с переполнением нет, какими бы искусственными они ни были.
Для целых чисел со знаком:
int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
div++;
Для целых чисел без знака:
int div = a / b;
if (a % b != 0)
div++;
Целочисленное деление ' /
' определено для округления до нуля (7.7.2 спецификации), но мы хотим округлить. Это означает, что отрицательные ответы уже округлены правильно, но положительные ответы необходимо скорректировать.
Ненулевые положительные ответы легко обнаружить, но нулевой ответ немного сложнее, так как это может быть либо округление отрицательного значения, либо округление положительного.
Самая безопасная ставка - определить, когда ответ должен быть положительным, проверив, что знаки обоих целых совпадают. Целочисленный оператор xor ' ^
' для этих двух значений приведет к появлению 0-значного бита, если это так, что означает неотрицательный результат, поэтому проверка (a ^ b) >= 0
определяет, что результат должен быть положительным перед округлением. Также обратите внимание, что для целых чисел без знака каждый ответ явно положительный, поэтому эту проверку можно опустить.
Остается только проверить, произошло ли какое-либо округление, для которого a % b != 0
выполнит работу.
Арифметика (целочисленная или иная) не так проста, как кажется. Мышление тщательно требуется всегда.
Кроме того, хотя мой окончательный ответ, возможно, не такой «простой» или «очевидный» или, возможно, даже «быстрый», как ответы с плавающей запятой, у него есть одно очень сильное искупительное качество для меня; Теперь я обдумал ответ, так что я уверен, что он правильный (пока кто-нибудь умнее не скажет мне иначе - скрытый взгляд в сторону Эрика -).
Чтобы получить такое же чувство уверенности в ответе с плавающей запятой, мне нужно больше (и, возможно, более сложно) подумать о том, существуют ли какие-либо условия, при которых точность с плавающей запятой может мешать, и Math.Ceiling
может ли что-то нежелательное на «правильных» входах.
Заменить (обратите внимание , я заменил второй myInt1
с myInt2
, предполагая , что было то , что вы имели в виду):
(int)Math.Ceiling((double)myInt1 / myInt2)
с участием:
(myInt1 - 1 + myInt2) / myInt2
Единственное предостережение в том, что если вы myInt1 - 1 + myInt2
переполните целочисленный тип, который вы используете, вы можете не получить то, что ожидаете.
Причина, по которой это неправильно : -1000000 и 3999 должны давать -250, это дает -249
РЕДАКТИРОВАТЬ:
Учитывая, что это имеет ту же ошибку, что и другое целочисленное решение для отрицательных myInt1
значений, может быть проще сделать что-то вроде:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
Это должно дать правильный результат при div
использовании только целочисленных операций.
Причина, по которой это неправильно : -1 и -5 должны давать 1, это дает 0
РЕДАКТИРОВАТЬ (еще раз, с чувством):
оператор деления округляется до нуля; для отрицательных результатов это совершенно верно, поэтому только неотрицательные результаты нуждаются в корректировке. Также учитывая, DivRem
что в любом случае выполняется a /
и a %
, давайте пропустим вызов (и начнем с простого сравнения, чтобы избежать вычисления по модулю, когда оно не нужно):
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
Причина, по которой это неправильно : -1 и 5 должны давать 0, это дает 1
(В свою защиту последней попытки я никогда не должен был пытаться дать обоснованный ответ, пока мой разум говорил мне, что я опоздал на 2 часа)
Прекрасный шанс использовать метод расширения:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
Это делает ваш код более читабельным:
int result = myInt.DivideByAndRoundUp(4);
Вы могли бы написать помощника.
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
Вы можете использовать что-то вроде следующего.
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
Некоторые из приведенных выше ответов используют числа с плавающей запятой, это неэффективно и действительно не нужно. Для беззнаковых целых это эффективный ответ для int1 / int2:
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
Для подписанных intts это не будет правильным
Проблема со всеми решениями здесь либо в том, что они нуждаются в приведении, либо в числовой проблеме. Кастинг на float или double всегда возможен, но мы можем добиться большего.
Когда вы используете код ответа от @jerryjvl
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
есть ошибка округления. 1/5 будет округлять вверх, потому что 1% 5! = 0. Но это неправильно, потому что округление произойдет только в том случае, если вы замените 1 на 3, поэтому результат равен 0,6. Нам нужно найти способ округления, когда вычисление дает нам значение, большее или равное 0,5. Результат оператора по модулю в верхнем примере имеет диапазон от 0 до myInt2-1. Округление произойдет только в том случае, если остаток больше 50% делителя. Итак, скорректированный код выглядит так:
int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
div++;
Конечно, у нас есть проблема с округлением на myInt2 / 2, но этот результат даст вам лучшее решение для округления, чем на этом сайте.