Как я могу гарантировать, что разделение целых чисел всегда округляется?

Я хочу, чтобы при необходимости округление целых чисел всегда округлялось. Есть ли лучший способ, чем это? Происходит много кастингов. 🙂

(int)Math.Ceiling((double)myInt1 / myInt2) 

ОБНОВЛЕНИЕ: Этот вопрос был предметом моего блога в январе 2013 года . Спасибо за большой вопрос!


Правильно использовать правильную арифметику целых чисел. Как было показано до сих пор, в тот момент, когда вы пытаетесь сделать «умный» трюк, шансы хорошие, что вы допустили ошибку. И когда обнаруживается недостаток, изменяя код, чтобы исправить недостаток, не учитывая, что исправление ломает что-то другое , не является хорошей методикой решения проблем. До сих пор я думал, что пять совершенно неправильных целочисленных арифметических решений для этой полностью не особо сложной проблемы.

Правильный подход к целочисленным арифметическим проблемам, то есть способ, который повышает вероятность правильного ответа в первый раз, – это тщательно подойти к проблеме, решить ее шаг за шагом и использовать хорошие инженерные принципы при выполнении так.

Начните с чтения спецификации того, что вы пытаетесь заменить. Спецификация для целочисленного деления четко заявляет:

  1. Деление округляет результат до нуля

  2. Результат равен нулю или положителен, когда два операнда имеют один и тот же знак и ноль или отрицательный, если два операнда имеют противоположные знаки

  3. Если левый операнд является наименьшим представимым int, а правый операнд равен -1, происходит переполнение. […] определяется реализация в отношении того, выбрано ли [Арифметическое исключение] или переполнение не отображается, а результирующее значение – в левом операнде.

  4. Если значение правого операнда равно нулю, генерируется исключение System.DivideByZeroException.

Нам нужна функция целочисленного деления, которая вычисляет фактор, но округляет результат всегда вверх , а не всегда к нулю .

Поэтому напишите спецификацию для этой функции. Наша функция int DivRoundUp(int dividend, int divisor) должна иметь поведение, определенное для каждого возможного ввода. Это неопределенное поведение вызывает глубокое беспокойство, поэтому давайте его устраним. Мы скажем, что наша операция имеет следующую спецификацию:

  1. срабатывает, если делитель равен нулю

  2. операция срабатывает, если дивиденд является int.minval и делитель равен -1

  3. если нет остатка – деление «четное» – тогда возвращаемое значение является интегральным фактором

  4. В противном случае он возвращает наименьшее целое число, большее, чем частное, то есть оно всегда округляется.

Теперь у нас есть спецификация, поэтому мы знаем, что можем придумать тестируемый дизайн . Предположим, мы добавили дополнительный проектный критерий, что проблема должна решаться исключительно с помощью целочисленной арифметики, а не вычислять частное как двойное, так как «двойное» решение явно отклонено в постановке задачи.

Итак, что мы должны вычислить? Ясно, что для удовлетворения нашей спецификации, оставаясь исключительно в целочисленной арифметике, нам нужно знать три факта. Во-первых, каково было целое число? Во-вторых, было ли разделение без остатка? И в-третьих, если нет, было ли целое число, вычисленное округлением вверх или вниз?

Теперь, когда у нас есть спецификация и дизайн, мы можем начать писать код.

 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; } 

Это умно? Прекрасно? Нет. Короче? Нет. Исправить в соответствии со спецификацией? Я так считаю, но я не полностью его протестировал. Это выглядит довольно неплохо.

Мы профессионалы здесь; используйте хорошие инженерные практики. Изучите свои инструменты, укажите желаемое поведение, сначала рассмотрите случаи ошибок и напишите код, чтобы подчеркнуть его очевидную правильность. И когда вы обнаружите ошибку, подумайте о том, был ли ваш алгоритм глубоко ошибочным, прежде чем вы просто случайно начнете менять порядок сравнения и сломать материал, который уже работает.

Окончательный ответ на основе int

Для целых чисел со знаком:

 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

EDIT (еще раз, с чувством):
Оператор деления округляется к нулю; для отрицательных результатов это совершенно верно, поэтому только неотрицательные результаты требуют корректировки. Также учитывая, что DivRem просто делает / и % любом случае, давайте пропустим вызов (и начнем с простого сравнения, чтобы избежать вычисления по модулю, когда это не нужно):

 int div = myInt1 / myInt2; if ((div >= 0) && (myInt1 % myInt2 != 0)) div++; 

Причина в том, что это неправильно : -1 и 5 должны давать 0, это дает 1

(В моей собственной защите последней попытки я никогда не должен был делать аргументированный ответ, пока мой разум говорил мне, что я опоздал на 2 часа)

Все ответы здесь пока слишком сложны.

В C # и Java для положительного дивиденда и делителя вам просто нужно сделать:

 ( dividend + divisor - 1 ) / divisor 

Источник: преобразование числа, Роланд Бакхаус, 2001

Отличная возможность использовать метод расширения:

 public static class Int32Methods { public static int DivideByAndRoundUp(this int number, int divideBy) { return (int)Math.Ceiling((float)number / (float)divideBy); } } 

Это также упрощает чтение вашего uber:

 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) 

Проблема со всеми решениями здесь заключается в том, что им нужен актерский состав или у них есть численная проблема. Кастинг для 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. Результат оператора modulo в верхнем примере имеет диапазон от 0 до myInt2-1. Округление будет происходить только в том случае, если остаток превышает 50% от делителя. Таким образом, скорректированный код выглядит следующим образом:

 int div = myInt1 / myInt2; if (myInt1 % myInt2 >= myInt2 / 2) div++; 

Конечно, у нас тоже проблема округления на myInt2 / 2, но этот результат даст вам лучшее решение округления, чем другие на этом сайте.

  • Что делать с высокой эффективностью Java BigDecimal?
  • Построение точки на краю сферы
  • К власти в C?
  • Как я могу рассчитать возраст человека в год, месяц, дни?
  • Существует ли стандартная функция знака (signum, sgn) в C / C ++?
  • Произвольные точные десятичные числа в C #?
  • Сортировка точек по часовой стрелке?
  • Перерисовать изображение с 3D-перспективы до 2d
  • Как проверить, является ли число мощностью 2
  • Как я могу разделить два целых числа, чтобы получить двойной?
  • Сделать сферу с эквидистантными вершинами
  • Давайте будем гением компьютера.