Как быстрее всего проверить, делится ли данное число на 15?

Деление в процессоре занимает много времени, поэтому я хочу спросить, как быстрее всего проверить, делится ли число на какое-то другое число, в моем случае мне нужно проверить, делится ли число на 15.

Также я просматривал Интернет и нашел забавные способы проверить, делится ли число на какое-то число, но я ищу быстрый вариант.

ПРИМЕЧАНИЕ: поскольку деление занимает много времени, я ищу ответ без / и %.


person Community    schedule 09.09.2013    source источник
comment
a % 15 == 0: доверяйте компилятору и оборудованию, чтобы сделать это эффективно.   -  person recursion.ninja    schedule 10.09.2013
comment
Модуль @awashburn в некоторых случаях не самый быстрый вариант   -  person ST3    schedule 10.09.2013
comment
@ user2623967 отсюда и слово доверять. Компилятор, скорее всего, лучше вас знает, как оптимизировать для локальной системы.   -  person recursion.ninja    schedule 10.09.2013
comment
@user2623967 user2623967 вероятно, так и есть, компилятор написан людьми, чья работа состоит в том, чтобы знать эти приемы, компиляторы очень хороши в оптимизации таких вещей, в отличие от реальных алгоритмов, поэтому в этом случае вы, вероятно, неправы   -  person aaronman    schedule 10.09.2013
comment
@awashburn Год назад я играл с подобными ситуациями и обнаружил, что можно написать лучший код, чем генерирует компилятор (конечно, не во всех случаях)   -  person ST3    schedule 10.09.2013
comment
@user2623967 user2623967 тогда докажи это, напиши тест, чтобы увидеть, работает ли твоя функция быстрее, чем мод, я думаю, это даже не близко к этому   -  person aaronman    schedule 10.09.2013
comment
@user2623967 user2623967 что-либо, кроме %, вряд ли будет оптимальным и портативным решением.   -  person recursion.ninja    schedule 10.09.2013
comment
Я рад, что получил ответ, но самая забавная часть получила большое количество отрицательных голосов.   -  person    schedule 10.09.2013
comment
@WhiteMan, причина, по которой за него изначально проголосовали, заключается в том, что кто-то опубликовал идею, в которой она не работала.   -  person aaronman    schedule 10.09.2013
comment
возможный дубликат Проверить, делится ли число на 3 ( мой ответ также имеет решение для N = 15)   -  person MSalters    schedule 10.09.2013
comment
@MSalters Я бы сказал, что это не обман, вопрос, на который вы ответили, касается того, как это сделать без /, % или *, когда все ответы здесь, кроме принятого, говорят, что вы должны просто использовать %, пожалуйста, не Закрыть   -  person aaronman    schedule 10.09.2013
comment
@awashburn: полагаться на то, что аппаратное обеспечение и компилятор сделают это эффективно, к сожалению, неверно, поскольку некоторые компиляторы не реализуют решения без разделения, тогда как другие, такие как gcc, делают это. Когда это не так, вы застряли с неэффективным кодом, и вам приходится прибегать к ручному внедрению кода.   -  person Olof Forshell    schedule 10.09.2013
comment
@P0W: разделение ДЕЙСТВИТЕЛЬНО требует времени, много времени. Не все компиляторы реализуют альтернативные, НАМНОГО более быстрые решения. Так что вы можете потерять веру в компиляторы. Это не весело, но иногда приходится.   -  person Olof Forshell    schedule 10.09.2013
comment
Даже GCC не делает это оптимально. Он использует алгоритм деления на умножение, а затем использует его для извлечения остатка, что намного лучше, чем простое деление по модулю, но не оптимально. Подробнее см. в главе 9: gmplib.org/~tege/divcnst-pldi94.pdf   -  person harold    schedule 13.09.2013
comment
@harold: который может быть заменен этой статьей: gmplib.org/~tege/division-paper .pdf   -  person Olof Forshell    schedule 09.04.2014


Ответы (6)


Умножение занимает меньше времени, чем деление, поэтому вы можете попробовать это:

inline bool divisible15(unsigned int x)
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
    return x * 4008636143u <= 286331153u;
}

Этот способ работает, потому что 2^32-1 (максимальное 32-битное значение) делится на 15, однако, если вы возьмете, например, 7, это будет выглядеть как работающее, но не будет работать во всех случаях.

EDIT: см. это, это доказывает, что это решение ( в некоторых компиляторах) работает быстрее, чем module.

EDIT: Вот объяснение и обобщение.

person ST3    schedule 09.09.2013
comment
Существуют рабочие решения для 7 и других значений, отличных от степени двойки. gcc реализует эти решения, когда делитель постоянен. Я сильно испугался, когда впервые увидел сгенерированную сборку. Но работает и быстро. - person Olof Forshell; 10.09.2013
comment
@OlofForshell Я использовал 7 в качестве примера числа, которое не работает в этом случае, я знаю, что есть другие способы проверить, делится ли значение, но в случае 15 это, вероятно, самый быстрый. - person ST3; 10.09.2013
comment
7 можно проверить таким же образом, но с использованием других констант. На данный момент метод умножения является самым быстрым, но кто сказал, что он никогда не изменится? - person Olof Forshell; 10.09.2013
comment
@OlofForshell хммм ... не уверен в этом, я думаю, что это может работать не со всеми значениями, можете ли вы дать мне эти константы? - person ST3; 10.09.2013
comment
В GCC это происходит не потому, что умножение занимает меньше времени, чем деление, а потому, что в этом конкретном тесте умножение в цикле оптимизировано (посредством оптимизации индукционной переменной) до того, как по модулю превращается в умножение + простая арифметика и сдвиги (первое преобразование RTL ). - person ninjalj; 10.09.2013
comment
При компиляции с -O3 модуль по модулю немного быстрее, чем метод умножения: /a/99507cde35389c48 - person interjay; 10.09.2013
comment
@user2623967: Я потратил некоторое время на поиски этого (homepage.cs.uiowa. edu/~jones/bcd/divide.html), которая объясняет более структурированный подход, и вы также можете проверить две публикации здесь (gmplib.org/~tege) относительно деления на инвариантные (постоянные) целые числа. - person Olof Forshell; 12.09.2013
comment
может быть, это потому, что я немного медленно понимаю вещи, которые я не могу понять, данное решение @ST3, не могли бы вы рассказать мне, как оно работает, означает, как написанный вами код гарантирует, что число равно div. до 15 в большинстве случаев. - person r.bhardwaj; 17.12.2013
comment
Ваша новая идея работает с 15. Она не работает с 30, которое в последний раз, когда я проверял, кратно 15. Это также дает ложный положительный результат для 31. Это просто проверка, является ли число на единицу меньше кратного 16. - person IronMensan; 06.10.2015
comment
@IronMensan о да .. это была быстрая идея, но плохая идея. Я вижу, что сейчас не так. Удаление его. - person ST3; 07.10.2015
comment
При использовании gcc -O3 -march=haswell оба цикла time() автоматически векторизуются с помощью AVX2. Однако divisible15 значительно меньше. Как и в случае со скаляром, ваш метод умножения и проверки выполняет гораздо меньше работы, чем реализация gcc %15. gcc использует аналогичное умножение на магическую константу, но выполняет полное умножение 32*32->64b и использует как старшую, так и младшую половину результата для чего-то. - person Peter Cordes; 15.02.2016

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

if (number % n == 0)

В большинстве случаев вы всегда можете это сделать, доверившись умным современным компиляторам.

Это не означает, что вы разочаровываетесь в изучении веселых способов. Проверьте эти ссылки.

Быстрые тесты на делимость (на 2,3,4,5,. ., 16)?

Небольшие хитрости

person max    schedule 09.09.2013
comment
Обязательный ответ? Учащиеся хотят учиться, действительно заинтересованные расширяют границы. Довериться умным современным компиляторам? Да правильно. В вашем примере я могу доверять умному современному компилятору использовать инструкцию деления 60+ тактов, что всегда делают компиляторы, когда делитель является переменным. Глупый ответ. - person Olof Forshell; 10.09.2013
comment
А что, если веселые способы — это лучшие, более быстрые и эффективные способы? Тогда они должны заменить текущие пути. - person Olof Forshell; 10.09.2013
comment
@OlofForshell Когда делитель является переменным, вы все равно мало что можете сделать. - person Erbureth says Reinstate Monica; 09.04.2014
comment
@Erbureth: почти всегда можно что-то сделать. Другое дело, что они могут быть более или менее осуществимы. Если программист знает диапазон значений для n, это может привести к открытиям. Конечно, для набора {2, 3, 4 ... 16} существуют общие возможности, которые могут снизить производительность набора {2, 4, 8, 16}, но ускорить другие. Обычно речь идет об обмене места на время, и если вы попали в затруднительное положение, какой у вас есть выбор, кроме как попробовать? Или, если вы хотите изучить возможности, узнать что-то самостоятельно, а не говорить, как что-то делать? - person Olof Forshell; 09.04.2014

Просто используйте i % 15 == 0


  1. Поскольку компилятор может легко увидеть, что 15 никогда не изменится, он может свободно выполнять любую оптимизацию операции мода. Это работа авторов компиляторов, чтобы сделать такую ​​​​оптимизацию, если они не придумали лучший способ сделать это, вы не будете.

  2. Например, очень легко проверить, делится ли число на 2, потому что вы просто проверяете первый бит. Разработчики компиляторов знают это, и вы можете сами написать код для проверки первого бита, но особенно зрелый компилятор заставит людей думать и работать над этими вещами годами. Этот тип оптимизации очень прост в выполнении, так как для этого требуется всего лишь изменить одну или две инструкции, таких оптимизаций, как лучшее распределение регистров, добиться гораздо сложнее.

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

  4. Поскольку вы на самом деле не проверили, что скорость имеет значение при написании кода, странный способ сделает код очень трудным для чтения для следующего человека и более подверженным ошибкам ( преждевременная оптимизация — корень всех зол)

  5. Он по-прежнему работает независимо от того, являются ли входные данные 16-, 32- или 64-битными, поскольку он не зависит от манипуляций с битами.

  6. Даже если автор компилятора не реализовал его, очевидно, что кто-то может его реализовать (даже вы сами)

person aaronman    schedule 09.09.2013
comment
5. Он по-прежнему работает, независимо от того, являются ли входные данные 16, 32 или 64 битами (или любого другого размера) - person Adam Rosenfield; 10.09.2013
comment
1. Что делать, если 15 непостоянно? У компилятора мало надежды на его оптимизацию, но если все возможные значения допускают методы, параллельные этому, можно было бы использовать решение с таблицей, индексируемой по модулю. 3. В целом верно, но вряд ли применимо к данному случаю. 4. Легко решается с помощью комментария. - person R.. GitHub STOP HELPING ICE; 10.09.2013
comment
@R .. в чем смысл, 15 в этом случае постоянно, очевидно, таблица модов работает только в том случае, если диапазон ввода мал, вы критикуете мой ответ, кстати, я не могу сказать - person aaronman; 10.09.2013
comment
Просто укажите некоторые причины, по которым может быть разумным вручную оптимизировать это, как просил OP. - person R.. GitHub STOP HELPING ICE; 10.09.2013
comment
Ааа, хорошо 1. если это не постоянно, тогда мод - очевидный выбор 3. привел меня туда 4. комментарий может решить читабельность, но он все еще более подвержен ошибкам, на самом деле причина, по которой принятый ответ был отклонен в первую очередь, заключается в том, что он не сработало, шансов испортить number%15==0 мало - person aaronman; 10.09.2013
comment
У меня есть проблемы с уважением к любому, кто разбрасывается преждевременной оптимизацией ... цитирует, не указывая, что в ней есть точные условия (3/97%), то есть когда это верно, а когда нет. Я чувствую, что для текущего коммерческого проекта справедливо условие 3%, хотя многие менеджеры проектов не согласятся. Если вы изучаете предмет для развлечения, удовольствия и развлечения, зачем вам вообще беспокоиться об этом выражении? - person Olof Forshell; 09.04.2014

В достаточно современном процессе деление на 15 не должно быть таким ужасным. Руководство по оптимизации AMD определяет его на основе частного (значение, которое делится), и оно занимает 8-битную позицию старшего бита частного. Итак, если ваши числа имеют 63-й бит, вы получите 71 цикл - что, конечно, довольно длинная инструкция. Но для 32-битного числа с несколькими нулями в старших битах мы говорим о 30-40 циклах. Если число умещается в 16-битном значении, максимальное значение составляет 23 цикла.

Чтобы получить остаток, требуется еще один тактовый цикл.

Если вы делаете это ВСЕ время, конечно, вы можете обнаружить, что это время довольно велико, но я не уверен, что есть тривиальный способ избежать этого.

Как уже говорили другие, компилятор может заменить его чем-то лучшим. Но 15, насколько мне известно, не имеет очевидного быстрого решения (если у вас 16 вместо 15, то мы можем использовать трюк x & 15).

Если это ограниченный диапазон, вы можете создать таблицу [vector<bool>, например, которая будет хранить 1 бит на запись], но довольно скоро вы столкнетесь с проблемой, что доступ к некэшированной памяти занимает столько же времени, сколько операция деления. ...

Есть несколько интересных способов выяснить, делится ли число на 3, 5 и т. д., путем суммирования цифр, но, к сожалению, они работают только на основе десятичных цифр, что включает в себя длинную последовательность делений.

person Mats Petersson    schedule 09.09.2013
comment
Есть тривиальный способ избежать этого, который называется делением на инвариантные целые числа с использованием умножения. Ответ пользователя 2623967 - на час раньше вашего - указывает на это возможное решение. Он существует уже давно и реализован в некоторых компиляторах, одним из которых является gcc. Думаю, вам нужно немного почитать. - person Olof Forshell; 10.09.2013

Вот еще один подход, который, вероятно, медленнее других, но использует только сложение, побитовое И и сдвиг:

int divisible15(unsigned int x) {
        if (x==0) return 1;
        x = (x & 0x0f0f0f0f) + ((x&0xf0f0f0f0)>>4);
        x = (x & 0x00ff00ff) + ((x&0xff00ff00)>>8);
        x = (x & 0x0000ffff) + ((x&0xffff0000)>>16);
        x = (x & 0x0f) + ((x&0xf0)>>4);
        return x==15;
}

Идея состоит в том, что делимость на 15 по основанию 16 аналогична делимости на 9 по основанию 10: сумма цифр должна делиться на 15.
Таким образом, код суммирует все шестнадцатеричные цифры (аналогично тому, как вы считаете биты). ), а сумма должна быть равна 15 (кроме 0).

person ugoren    schedule 08.10.2013
comment
Вы можете выполнить сдвиг перед маскированием, чтобы каждая строка использовала одну и ту же константу дважды. Это, вероятно, помогло бы в архитектуре RISC, где большие непосредственные константы должны быть созданы в регистрах с несколькими инструкциями (в отличие от x86, где любая инструкция может иметь 32-битное непосредственное значение). - person Peter Cordes; 15.02.2016
comment
@PeterCordes, наверное, да. Что-то вроде x = (x & 0x0f0f0f0f) + ((x>>4) & 0x0f0f0f0f); - person ugoren; 15.02.2016

Ну, это очень легко сделать в уме, если у вас есть шестнадцатеричное представление. Просто суммируйте все цифры, пока не получите одну цифру. Если ответ «0xf», он делится на 15.

Пример 0x3a98 : 3 + 0xa + 9 + 8 = 0x1e = 1 + 0xe = 0xf, так что это число делится на 15.

Это работает для всех факторов на X-1, где X — это основание, используемое для представления числа. (Для меньших множителей последняя цифра должна делиться на множитель).

Однако не ожидайте, что это будет быстро в коде.

person Roddy    schedule 29.01.2016