PHP - 帮助修复由两个数字划分引起的错误

时间:2009-12-07 21:56:33

标签: php debugging

我一直在处理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 (http://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时,因为它是我的三个权力的基础。结果它完全分开,因为使用'floor'它没有被注意到,脚本试图添加不存在的第62个字符,只产生一个三字符代码而不是四个...因此'aa'是结果而不是'aaab'。

以下是代码的问题部分:

        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 Vanzonneveld撰写:kevin dot vanzonneveld dot net,由Simon Franz修改:blog dot snaky dot org并由Stackoverflows优化自己的mattbasta

3 个答案:

答案 0 :(得分:1)

你走了:

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 (http://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.这是因为浮点变量的大小有限。

这就是为什么它不起作用。

我编写了一个函数preciseDivision,它会自动为该部门添加一个非常小的值,以便完成此任务。

我仍然相信这个网址散列问题应该存在更清晰的解决方案。我会看到我能做些什么。

答案 1 :(得分:1)

根据您对其他问题的my answer,尝试将log10($in) / log10($base)替换为log($in, $base)

这避免了将两个对数的结果除以浮点数相关联的不准确性,并为您提供正确的结果。

答案 2 :(得分:0)

添加另一个答案作为第一个答案也有效,尽管这个答案更清晰。

我摆脱了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 (http://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 (http://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;
}