Округление целочисленного деления в меньшую сторону в C # (для отрицательных значений)

#c# #math

#c# #математика

Вопрос:

Есть ли какой-либо способ выполнить целочисленное деление в C # (без числа с плавающей запятой или десятичного числа, мне нужно сделать это очень быстро), который округляет число в меньшую сторону?

Деление по умолчанию просто отбрасывает аргумент дроби. Рассмотрим:

  1 / 2 = 0 // That is correct
-1 / 2 = 0 // ... but I want to get (-1) here!
  

Мое деление будет принимать как положительные, так и отрицательные числа для делителя. Мне не нужно использовать if , поскольку ветвление будет слишком дорогостоящим (операция будет выполняться очень часто в игровом движке реального времени)…

Комментарии:

1. Вычтите 1 из отрицательного числа перед его делением (не делайте этого для неотрицательных чисел)

2. @elgonzo Я не знаю, является ли число отрицательным. Оно может быть как положительным, так и отрицательным

3. Конечно, вы можете это знать, другими словами, ваш код может определить, является ли число отрицательным или нет…

4. Что такого в числах с плавающей запятой, что вы считаете медленным? Возможно, вы представляете проблему, которой не существует.

5. @elgonzo Для этого мне нужна if или любая другая форма ветвления, что является очень дорогостоящей операцией

Ответ №1:

Деление на 2 — это простой побитовый сдвиг вправо. После упоминания @elgonzo (теперь удаленного) о свойствах rightshift в C # я решил посмотреть, как это работает, и, кажется, это делает именно то, что вы хотите:

 var result = number >> 1;
  

Это дает следующие результаты:

11 -> 5
10 -> 5
2 -> 1
1 -> 0
-1 -> -1
-2 -> -1
-32 -> -16
-33 -> -17

int.MaxValue и MinValue также работают.

С точки зрения производительности это, кажется, почти в два раза быстрее, чем принятый в настоящее время ответ, использующий операторы по модулю. Деление (тех же самых) 100000000 случайных чисел занимает 2,17 секунды на моей машине с использованием простого сдвига, в то время как использование версии с модулем занимает от 3,1 до 4,0 секунды.

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

Комментарии:

1. Черт возьми! Невозможность увидеть лес для всех деревьев. Вот, мой голос за. (Как бы то ни было, я удалил свой слишком сложный ответ, пытаясь не отвлекать от простоты решения. Чувак, могу ли я винить в этом выходные? ;-P )

2. @elgonzo Что ж, ты указал мне правильное направление, спасибо за это 🙂

Ответ №2:

Если вы хотите разделить a на b :

Подход, который не подведет из-за переполнения:

 int res = a / b;
return (a < 0 amp;amp; a != b * res) ? res - 1 : res;
  

Следующие подходы могут привести к сбою из-за отрицательного переполнения.

 int mod = a % b;
if (mod < 0) {
  mod  = b;
}
return (a - mod) / b;
  

Путаница с mod = b because a % b может быть отрицательной.
Более короткий способ:

 return (a - (a % b   b) % b) / b;
  

И более понятный:

 return a >= 0 ? a / b : (a - b   1) / b;
  

Комментарии:

1. Я только что понял, что существует риск отрицательного переполнения.

2. Обратите внимание, что операции по модулю могут быть более дорогостоящими, чем простое ветвление, при рассмотрении возможностей прогнозирования ветвления процессора (возможное влияние на прирост производительности прогнозирования ветвления из-за устранения некоторых недавних уязвимостей процессора, таких как Spectre / Meltdown, которые не выдерживают)

3. в 3 версиях используется ветвление, а в четвертой используются 2 операции по модулю. Я бы предложил протестировать производительность…

4. После тестирования, похоже, что все эти опции работают значительно медленнее, чем простой n>>1 , при выполнении той же работы.

5. @oerkelens, Ну, конечно. Но n >> 1 делится только на 2 . Вы должны обрабатывать другие целые числа.