WedX - журнал о программировании и компьютерных науках

PHP - помогите исправить ошибку, вызванную точным делением двух чисел

Я работал с фрагментом кода для URL-адресов в стиле YouTube, но нашел ошибку и надеюсь, что кто-нибудь покажет мне наиболее эффективный способ ее исправить.

function alphaID($in, $to_num = false, $pad_up = false, $passKey = null)
{
    static $passcache;
        if(empty($passcache))
                $passcache = array();

    $index = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $i = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
    if (!empty($passKey)) {
        // Although this function's purpose is to just make the
        // ID short - and not so much secure,
        // with this patch by Simon Franz (https://blog.snaky.org/)
        // you can optionally supply a password to make it harder
        // to calculate the corresponding numeric ID

                if(isset($passcache[$passKey]))
                        $index = $passcache[$passKey];
                else {
                        if(strlen($passhash = hash('sha256',$passKey)) < strlen($index))
                                $passhash = hash('sha512',$passKey);

                        $p = str_split($passhash);

                        array_multisort($p, SORT_DESC, $i);
                        $index = implode($i);
                        $passcache = $index;
                }
    }

    $base = strlen($index);

    if ($to_num) {
        // Digital number <<-- alphabet letter code
        $in = strrev($in);
        $out = 0;
        $len = strlen($in) - 1;
        for ($t = 0; $t <= $len; $t++) {
            $bcpow = bcpow($base, $len - $t);
            $out += strpos($index, $in[$t]) * $bcpow;
        }

        if (is_numeric($pad_up)) {
            $pad_up--;
            if ($pad_up > 0) {
                $out -= pow($base, $pad_up);
            }
        }
    } else {
        // Digital number -->> alphabet letter code
        if (is_numeric($pad_up)) {
            $pad_up--;
            if ($pad_up > 0) {
                $in += pow($base, $pad_up);
            }
        }

        $out = "";
        for ($t = floor(log10($in) / log10($base)); $t >= 0; $t--) {
                $bcp = bcpow($base, $t);
            $a = floor($in / $bcp);
            $out .= $index[$a];
            $in -= $a *  $bcp;
        }
        $out = strrev($out); // reverse
    }

    return $out;
}

Ошибка только при кодировании одного числа 238328, так как это моя база в степени три. В результате он делится точно, и из-за использования «пола» он остается незамеченным, и скрипт пытается добавить 62-й символ, которого не существует, и создает только трехсимвольный код, а не четыре ... таким образом, «аа» является результатом а не "аааб".

Вот проблемная часть кода:

        for ($t = floor(log10($in) / log10($base)); $t >= 0; $t--) {
                $bcp = bcpow($base, $t);
            $a = floor($in / $bcp);
            $out .= $index[$a];
            $in -= $a *  $bcp;

И чтобы было еще проще, вот вызов, чтобы получить ошибку

echo alphaID(238328);

кредиты: Первоначально написано Кевином Ванзонневельдом: kevin dot vanzonneveld dot net, изменено Саймоном Францем: blog dot snaky dot org и оптимизировано собственной матбастой Stackoverflows

07.12.2009

Ответы:


1

Добавление другого ответа в качестве первого также работает, хотя этот чище.

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

function alphaID($in, $to_num = false, $pad_up = false, $passKey = null)
{
    static $passcache;
        if(empty($passcache))
                $passcache = array();

    $index = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $i = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
    if (!empty($passKey)) {
        // Although this function's purpose is to just make the
        // ID short - and not so much secure,
        // with this patch by Simon Franz (https://blog.snaky.org/)
        // you can optionally supply a password to make it harder
        // to calculate the corresponding numeric ID

                if(isset($passcache[$passKey]))
                       $index = $passcache[$passKey];
                else {
                        if(strlen($passhash = hash('sha256',$passKey)) < strlen($index))
                                $passhash = hash('sha512',$passKey);

                        $p = str_split($passhash);

                        array_multisort($p, SORT_DESC, $i);
                        $index = implode($i);
                        $passcache = $index;
                }
    }

    $base = strlen($index);

    if ($to_num) {
        // Digital number <<-- alphabet letter code

        // A conversion from base $base to base 10

        $out = 0;           // End number
        $shift = 1;         // Starting shift
        $len = strlen($in); // Length of string

        for ($t = 0; $t < $len; $t++) 
        {
            $out += strpos($index, $in[$t]) * $shift; // $out is a number form alphabet * base^shift
            $shift *= $base;  // increase shift
        }       


        if (is_numeric($pad_up)) {
           $pad_up--;
           if ($pad_up > 0) {
               $out -= pow($base, $pad_up);
            }
        }
    } else {
        // Digital number -->> alphabet letter code
        if (is_numeric($pad_up)) {
            $pad_up--;
            if ($pad_up > 0) {
                $in += pow($base, $pad_up);
            }
        }

        $out = "";

        // A simple conversion from base 10 to base $base

        while ($in > 0)
        {
            $remainder = $in % $base;
            $in = intval(($in-$remainder)/$base);

            $out .= $index[$remainder];
        }

    }

    return $out;
}

Код стал чище и должен быть быстрее. Теперь гораздо легче увидеть, что это всего лишь преобразование из базы 10 в базу $base (62?) и наоборот. Он не включает деление с плавающей запятой, поэтому в нем нет упомянутой выше ошибки.

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

Добавлена ​​математика BC, как вы сказали, вам нужны большие целые числа

function alphaID($in, $to_num = false, $pad_up = false, $passKey = null)
{
   static $passcache;
       if(empty($passcache))
               $passcache = array();

   $index = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
   $i = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
   if (!empty($passKey)) {
       // Although this function's purpose is to just make the
       // ID short - and not so much secure,
       // with this patch by Simon Franz (https://blog.snaky.org/)
       // you can optionally supply a password to make it harder
       // to calculate the corresponding numeric ID

               if(isset($passcache[$passKey]))
                      $index = $passcache[$passKey];
               else {
                       if(strlen($passhash = hash('sha256',$passKey)) < strlen($index))
                               $passhash = hash('sha512',$passKey);

                       $p = str_split($passhash);

                       array_multisort($p, SORT_DESC, $i);
                       $index = implode($i);
                       $passcache = $index;
               }
   }

   $base = strlen($index);

   if ($to_num) {
       // Digital number <<-- alphabet letter code

       // A conversion from base $base to base 10

       $out = '0';           // End number
       $shift = 1;         // Starting shift
       $len = strlen($in); // Length of string

       for ($t = 0; $t < $len; $t++) 
       {
           $out = bcadd($out, bcmul(strpos($index, $in[$t]),$shift)); // $out is a number from alphabet * base^shift
           $shift = bcmul($shift, $base);  // increase shift
       }       


       if (is_numeric($pad_up)) {
          $pad_up--;
          if ($pad_up > 0) {
              $out -= pow($base, $pad_up);
           }
       }
   } else {
       // Digital number -->> alphabet letter code
       if (is_numeric($pad_up)) {
           $pad_up--;
           if ($pad_up > 0) {
               $in += pow($base, $pad_up);
            }
        }

        $out = "";

        // A simple conversion from base 10 to base $base

        while ($in > '0') // We're treating integer as a string, so BC math works
       {
            $remainder = bcmod($in,$base);
            $in = bcdiv($in, $base);

            $out .= $index[$remainder];
        }

    }

    return $out;
}
08.12.2009
  • Я обнаружил, что максимальное число, которое он успешно сделает, равно 2147483647... как я могу увеличить его (на всякий случай)? 08.12.2009
  • Все, о чем я могу думать, это... разбить его, чтобы, если его 2147483648 стало 2147483647 и 1 ... проблема в том, как его расшифровать. Возможно, если первые 6 символов будут bBCuvc, то к результату декодирования 7-го... 8-го... 9-го символа будет добавлено 2147483648. 08.12.2009
  • Это не работает, так как число 2147483647 = 2 ^ 31 -1 является наибольшим целым числом, которое может быть сохранено в 32 битах со знаком, что является нормальным размером целого числа. Если действительно нужно делать большие числа, используйте математику BC (я отредактирую свой пост, чтобы добавить решение). Но если бы я был вами, я бы спросил себя, действительно ли вам когда-нибудь понадобится более 2 миллиардов идентификаторов для кодирования? Для хранения только их числовых представлений потребуется около 268 МБ данных. 08.12.2009
  • Я также хотел бы добавить, что использование математики BC намного менее эффективно, чем использование встроенной математики (математики, которые работают только до 2 ^ 31-1). 08.12.2009
  • Вау... ты гений :D Большое спасибо 09.12.2009

  • 2

    Ну вот:

    function preciseDivision($x,$y)
    {
        // Correct floor's failures by adding a bit of overhead
        $epsilon = 0.00000001;
        return floor(($x/$y) + $epsilon);
    }
    function alphaID($in, $to_num = false, $pad_up = false, $passKey = null)
    {
        static $passcache;
        if(empty($passcache))
                $passcache = array();
    
        $index = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $i = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
        if (!empty($passKey)) {
           // Although this function's purpose is to just make the
           // ID short - and not so much secure,
           // with this patch by Simon Franz (https://blog.snaky.org/)
           // you can optionally supply a password to make it harder
           // to calculate the corresponding numeric ID
    
                   if(isset($passcache[$passKey]))
                           $index = $passcache[$passKey];
                   else {
                           if(strlen($passhash = hash('sha256',$passKey)) < strlen($index))
                                   $passhash = hash('sha512',$passKey);
    
                           $p = str_split($passhash);
    
                           array_multisort($p, SORT_DESC, $i);
                           $index = implode($i);
                           $passcache = $index;
                   }
       }
    
       $base = strlen($index);
    
       if ($to_num) {
           // Digital number <<-- alphabet letter code
           $in = strrev($in);
           $out = 0;
           $len = strlen($in) - 1;
           for ($t = 0; $t <= $len; $t++) {
               $bcpow = bcpow($base, $len - $t);
               $out += strpos($index, $in[$t]) * $bcpow;
           }
    
           if (is_numeric($pad_up)) {
               $pad_up--;
               if ($pad_up > 0) {
                   $out -= pow($base, $pad_up);
               }
           }
       } else {
           // Digital number -->> alphabet letter code
           if (is_numeric($pad_up)) {
               $pad_up--;
               if ($pad_up > 0) {
                   $in += pow($base, $pad_up);
               }
           }
    
           $out = "";
    
           for ($t = preciseDivision(log10($in),log10($base)); $t >= 0; $t--) {
    
               $bcp = bcpow($base, $t);
    
               $a = preciseDivision($in, $bcp);
               $out .= $index[$a];
               $in -= $a *  $bcp;
           }
           $out = strrev($out); // reverse
       }
    
       return $out;
    }
    

    Проблема здесь была не в полу, а в точности с плавающей запятой. В результате деления получилось 2,99999999, а пол(2,999999) равен 2, а не 3. Это происходит из-за ограниченного размера переменных с плавающей запятой.

    Вот почему это не сработало.

    Чтобы пройти через это, я написал функцию точного деления, которая автоматически добавляет к делению очень маленькое значение.

    И я по-прежнему считаю, что должны существовать более чистые решения этой проблемы хеширования URL-адресов. Я посмотрю что я могу сделать.

    07.12.2009
  • это генератор URL в стиле ютуба 08.12.2009
  • Что делает генератор URL-адресов в стиле YouTube? 08.12.2009
  • Он используется для преобразования больших числовых идентификаторов в URL-адресах в более короткие числовые строки alhpa. Он принимает int и преобразует его по основанию 62... превращая обычное число 0-9 в a-z-A-Z-0-9. 08.12.2009

  • 3

    В соответствии с мой ответ на ваш другой вопрос , попробуйте заменить log10($in) / log10($base) на log($in, $base).

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

    08.12.2009
    Новые материалы

    Как создать диаграмму градиентной кисти с помощью D3.js
    Резюме: Из этого туториала Вы узнаете, как добавить градиентную кисть к диаграмме с областями в D3.js. Мы добавим градиент к значениям SVG и применим градиент в качестве заливки к диаграмме с..

    Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что это выглядит сложно…
    Просто начните и учитесь самостоятельно Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что он кажется мне сложным, и я бросил его. Это в основном инструмент..

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

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..


    Для любых предложений по сайту: [email protected]