1. Си / Говнокод #3695

    +131

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    #include <stdio.h>
    
    void factorization(int num, int show) {
        int num1 = num;
        int n = 2;
        while ( n*n <= num1 ) {
            if ( num%n == 0 ) {
                num = num / n;
                if ( show )
                    printf( "%d\n", n );
            } else {
                n ++;
            }
        }
    }
    
    int main() {
        int i = 0;
        while ( i < 1000 ) {
            factorization(999999, 0);
            i ++;
        }
        return 0;
    }

    Опубликовано в одной из ссылок с http://habrahabr.ru/blogs/ruby/48952/ (если надо, точную ссылку найду позже).
    Код раскладывает число на простые множители тупым перебором делителей. Мало того, что этот код медленный, так он иногда последний множитель пропускает. Одновременно и ошибка, и скорость исправляются так:
    - while ( n*n <= num1 ) {
    + while ( n <= num ) {
    Неожиданно, правда?

    Запостил: inkanus-gray, 13 Июля 2010

    Комментарии (18) RSS

    • Это же тест на скорость. Нашёл к чему придираться, пипец.
      Ответить
      • Соглашусь. Поскольку это тест на скорость, код не обязан быть оптимальным.
        Ответить
    • У него руби победил с 287 мс, а у меня .NET выполнил тот же тест за 7 мс хыхы (что моно, что MS NET)

      Ну ладно, пусть детишки дальше в игрушки играются
      Ответить
      • Отсюда мораль: не верить ничему, что пишут на околокомпьютерных ресурсах.
        Ответить
        • особенно на хабрахабре
          Ответить
          • я там ржал, когда увидел статейку "Программирование микроконтроллеров для начинающих"
            Ответить
            • Всё когда-то начинается. Для начинающих в программировании контроллеров.
              К тому же микроконтроллер это всё таки не большой адронный коллайдер.
              Так что имеет место быть.
              Ответить
    • >>>>Одновременно и ошибка, и скорость исправляются так:
      >>>>while ( n*n <= num1 ) {
      >>>>+ while ( n <= num ) {
      Ыыы)))
      да что вы тут такое пишете - вы походу вообще не в теме

      1. перебирать надо до (корня от n)+1 - вычисленного 1 раз (квадратичный спид-ап) именно для этого там n*n, но зачем каждый раз умножать если можно взять корень от num1
      2. перебирать надо нечетные со степом 2 - спид-ап в два раза от пункта 1,
      проверив предварительно двойку
      3. если хотим большего надо пользовать формулу 6k+1, 6*k-1 - спидап в полтора раза от пункта 2.

      это самое примитивное, если найду запостю труЪ алгоритм, короче самое быстрое что мне удавалось писать раскладывало числа до 2^64 менее чем за секунду кажись.
      правда это был асм, FPU, SSE и еще пару хитрых оптимизаций
      Ответить
      • код - безусловное с точки зрения скорости говно, инканус - ты не обижайся твоя оптимизация тоже Г

        вообще если кому надо математическое обоснование - пишите.
        ЗЫ в пункте 3 надо сначало проверить 2 и 3 а потом юзать формулу 6k+1, 6*k-1
        Ответить
        • вот примерно таким образом ищется первый делитель.
          несложно сделать из этого факторизацию, даже рекурсивный способ должен работать быстрее вышеприведенных

          if ( num%2 == 0 ) return 2;
          if ( num%3 == 0 ) return 3;
          int root=((int) sqrt(num))+1;
          int k=(root/6)+1;

          for (int i=1,div=0;i<k;i++){
          div+=6;
          if ( num%(div-1) == 0 ) return (div-1);
          if ( num%(div+1) == 0 ) return (div+1);
          }
          //число простое
          return -1;
          Ответить
      • Никаких обид. Про необходимость пропускать чётные я хотел написать, но не стал писать умышленно, потому что мне было интересно, заметит ли кто-нибудь. Пропускать тройки тоже неплохо. А вот дальше, боюсь, оптимизация будет сомнительной.
        Про корень от num1 я тоже думал, но с ним получается медленнее. Надо будет выяснить, почему.

        Кто-нибудь заметил, что в предложенном патче num вместо num1? Цикл автоматически остановится на последнем множителе.
        Pro et contra
        Pro: в данном примере последняя итерация будет, когда n == num == 37 вместо n == sqrt(num1) == 999.
        Contra: для чисел вида 2*x, 3*x, 5*x, где x — простой множитель, намного больший, чем sqrt(num1), будет медленно.
        Резюме: использовать комбинированное условие для завершения.
        Ответить
        • Про корень вру. С корнем всего на 5% быстрее, а хотелось бы больше.
          Ответить
          • попробуй числа побольше, начиная с 2^24 ))
            ибо корень первая и наиглавнейшая оптимизация
            Ответить
        • >>>>А вот дальше, боюсь, оптимизация будет сомнительной.
          нет, 30*n и исключение 5 - точно быстрее, дальше - зависит от того какие простые числа,
          если маленькие - увеличивается размер кода, и он не влазит в L1 - что снижает скорость
          если большие - там можно цикл намутить и бренчинг кроется выигрышем в пропусках чисел кратных 7 и 11
          но тут надо генить таблицу.
          30*n - весьма оптимальное и простое сочетание

          >>>>Про корень от num1 я тоже думал, но с ним получается медленнее.
          никак нет, выигрыш в скорости - просто нереальный (квадратичный), попробуй то что я набросал на числах около 100 миллионов.
          пример так надо перебирать до миллиона - n
          а так до тысячи - корень из n
          но ВАЖНО корень он не для факторизации - он для проверки на простоту (или поиска первого делителя)
          Ответить
          • вообще по этой теме можно диссертации писать.
            тут для разных диапазонов чисел разные оптимальные алгоритмы ))))
            Ответить
          • вообще самая быстрая проверка - это сгенить таблицу простых чисел,
            но т.к. в те времена когда я этим занимался она не влазила в мои 512Мб, пришлось делать хитрые вещи - типа этих 6*n и 30*n, а хранить числа либо как массив битов простое - не простое (причем хранились только значения для n) (и массив битов - на самом деле был байтовым)

            либо как разницу между простыми числами, деленную пополам т.к. она почти всегда была меньше байта, то одно простое число занимало байт.
            + у меня был массив исключений для разниц, которые были больше 512 (напоминаю я делю разницу пополам, поскольку она всегда четная)
            Ответить
            • >>>(и массив битов - на самом деле был байтовым)
              то есть я получал биты через инлайн-спец-функцию меняющую бит адресацию на байт и извлекающую их из байтового массива
              Ответить
        • >>>>Contra: для чисел вида 2*x, 3*x, 5*x, где x — простой множитель, намного больший, чем sqrt(num1), будет медленно.

          а мы х - будем проверять на простоту тоже по корню )))) если простое - напечатали и вышли, нет - нашли множитель итд..
          я же сказал рекурсивно, но я делал и без рекурсии
          Ответить

    Добавить комментарий