将JavaScript函数转换为PHP

7
我有一个带有数组输入的JS函数。
例如:
x=[ 239709880, 250229420, 109667654, 196414465, 13098 ] y=[ 78135241, 54642792, 249 ]
或者:
x=[ 0, 0, 0, 0, 0, 0, 1 ] y=[ 78135241, 54642792, 249 ]
或者:
x=[ 49 ] y=[ 33 ]
function bdiv(x,y) {
    var n=x.length-1, t=y.length-1, nmt=n-t, arr = []
    if(n < t || n==t && (x[n]<y[n] || n>0 && x[n]==y[n] && x[n-1]<y[n-1])) {
        arr['q']=[0]
        arr['mod']=x
        return arr
    }
    if(n==t && toppart(x,t,2)/toppart(y,t,2) <4) {
        var q=0, xx
        for(;;) {
            xx=bsub(x,y)
            if(xx.length==0) break
            x=xx; q++
        }
        arr['q']=[q]
        arr['mod']=x
        return arr
    }
    var shift, shift2
    shift2=Math.floor(Math.log(y[t])/log2)+1
    shift=bs-shift2

    if(shift) {
        x=x.concat()
        y=y.concat()
        for(i=t; i>0; i--) y[i]=((y[i]<<shift) & bm) | (y[i-1] >> shift2); y[0]=(y[0]<<shift) & bm
        if(x[n] & ((bm <<shift2) & bm)) { x[++n]=0; nmt++; }
        for(i=n; i>0; i--) x[i]=((x[i]<<shift) & bm) | (x[i-1] >> shift2); x[0]=(x[0]<<shift) & bm
    }
    var i, j, x2, y2,q=zeros(nmt+1)

    y2=zeros(nmt).concat(y)

    for(;;) {
        x2=bsub(x,y2)
        if(x2.length==0) break
        q[nmt]++
        x=x2
    }
    var yt=y[t], top=toppart(y,t,2)
    for(i=n; i>t; i--) {
        m=i-t-1
        if(i >= x.length)
            q[m]=1
        else if(x[i] == yt)
            q[m]=bm
        else
            q[m]=Math.floor(toppart(x,i,2)/yt)
        topx=toppart(x,i,3)
        while(q[m] * top > topx)
            q[m]--
        y2=y2.slice(1)
        x2=bsub(x,bmul([q[m]],y2))
        if(x2.length==0) {
            q[m]--
            x2=bsub(x,bmul([q[m]],y2))
        }
        x=x2
    }
    if(shift){
        for(i=0; i<x.length-1; i++)
            x[i]=(x[i]>>shift) | ((x[i+1] << shift2) & bm);
        x[x.length-1]>>=shift
    }
    while(q.length > 1 && q[q.length-1]==0)
        q=q.slice(0,q.length-1)
    while(x.length > 1 && x[x.length-1]==0)
        x=x.slice(0,x.length-1)
    arr['q']=q
    arr['mod']=x
    return arr;
}


到目前为止,我在PHP方面已经做了五天的工作:

function bdiv($x,$y){
    global $bs, $bm, $bx2, $bx, $bd, $bdm, $log2;
    $arr=[];
    $n=count($x)-1;
    $t=count($y)-1;
    $nmt=$n-$t;

    if($n < $t || $n==$t && ($x[$n]<$y[$n] || $n>0 && $x[$n]==$y[$n] && $x[$n-1]<$y[$n-1]))
        return ['q'=>[0], 'mod'=>$x];

    if($n==$t && toppart($x,$t,2)/toppart($y,$t,2) <4){
        $q=0;
        for(;;){
            $xx=bsub($x,$y);
            if(count($xx)==0)
                break;
            $x=$xx;
            $q++;
        }
        return ['q'=>[$q], 'mod'=>$x];
    }

    $shift2=floor(log($y[$t])/$log2)+1;
    $shift=$bs-$shift2;
    if($shift){

/////////////////////////////////////////////// Booboo
        //$x = array_merge(array(),$x);
        //$y = array_merge(array(),$y);

        for($i=$t; $i>0; $i--)
            $y[$i]=(($y[$i] << $shift) & $bm) | ($y[$i-1] >> $shift2);
        $y[0]=($y[0] << $shift) & $bm;
        if($x[$n] & (($bm << $shift2) & $bm)){
            $x[++$n]=0;
            $nmt++;
        }
        for($i=$n; $i > 0; $i--)
            $x[$i]=(($x[$i] << $shift) & $bm) | ($x[$i-1] >> $shift2);
        $x[0]=($x[0] << $shift) & $bm;
    }
    $q=zeros($nmt+1);

    //array_push($arr, zeros($nmt));
    //array_push($arr, $y);
    //$y2=array_merge(...$arr);
    //////////////////////////////////// use array_merge straight away
    $y2=array_merge(zeros($nmt),$y);

    for(;;){
        $x2=bsub($x,$y2);
        if(count($x2)==0)
            break;
        $q[$nmt]++;
        $x=$x2;
    }

    $yt=$y[$t];
    $top=toppart($y,$t,2);

    for($i=$n; $i>$t; $i--){
        $m=$i-$t-1;
        if($i >= count($x))
            $q[$m]=1;
        else if($x[$i] == $yt)
            $q[$m]=$bm;
        else
            $q[$m]=floor(toppart($x,$i,2)/$yt);

        $topx=toppart($x,$i,3);
        while($q[$m] * $top > $topx)
            $q[$m]--;

        $y2=array_slice($y2,1);
        $x2=bsub($x,bmul([$q[$m]],$y2));

        if(count($x2)==0){
            $q[$m]--;
            $x2=bsub($x,bmul([$q[$m]],$y2));
        }
        $x=$x2;
    }

    if($shift){
        for($i=0; $i<count($x)-1; $i++)
            $x[$i]=($x[$i] >> $shift) | (($x[$i+1] << $shift2) & $bm);
        $x[count($x)-1] >>= $shift;
    }

    while(count($q) > 1 && $q[count($q)-1]==0)
        $q=array_slice($q, 0, count($q)-1);
    while(count($x) > 1 && $x[count($x)-1]==0)
        $x=array_slice($x, 0, count($x)-1);

    return ['q'=>$q, 'mod'=>$x];
}

我在PHP代码中标记了问题,array_push($x,$x)存在问题,看起来这不等同于x=x.concat()。 array_push会将整个当前 $x 值作为一个新元素添加到现有的$x数组中:

$x=[ 1, 2, 3 ];
array_push($x,$x);
那么,$x 将变成[ 1, 2, 3, [ 1, 2, 3 ] ]

如果尝试展开数组($x=array_merge(...$x);),则会出现新的PHP错误:array_merge(): Argument #1 is not an array

如果有任何人有任何想法,如何将此JS函数正确转换为PHP版本,我会非常感激。提前致谢。

==========================> UPDATE I

@Kiran Shakya的主意,用$x=array_merge(array(),$x);替换x=x.concat()实际上是有效的,或者至少我没有收到PHP错误或警告,但会启动一个无限循环,我必须手动关闭。调用toppart函数的脚本处理任意精度数字(乘和加):

    function toppart(x,start,len) {
        var n=0
        while(start >= 0 && len > 0){
            n=n*bx2+x[start--]
            len--
        }
        return n
    }

有趣的是,对于示例70144566321522750,JS返回了该数字,但是PHP返回了70144566321522751。在后续的循环中,差异要大得多。
我已经检查了两个版本中所有数字,并且输入x、start、len和bx2都相同:这可能是一个错误,或者其中一个无法处理大整数,或者还有其他原因吗?

==========================> 更新 II

我采用了Booboo的解决方案,完全跳过了concat()部分。

因此,输入为:

$x=[ 210763776, 109357119, 261308872];
$start=2;
$len=2;
$bx2=268435456;

...在PHP中返回了70144566321522751,在JS中返回了70144566321522750。我使用了bcadd()和bcmul(),但是如果我使用数学运算符号,结果也是相同的。

function toppart($x,$start,$len){
    global $bs, $bm, $bx2, $bx, $bd, $bdm, $log2;
    $n=0;
    while($start >= 0 && $len > 0){
        $n= bcadd(bcmul($n, $bx2),$x[$start--]);
        $len--;
    }

    return $n;
}

3
通过谷歌代码,它看起来是一个任意精度除法函数。如果是这样的话,为什么不直接使用bcmath的bcdiv()呢? - AKX
@AKX 没错,这就是我的版本!不幸的是,我对这个函数不熟悉,经过一些阅读后,我发现输入应该是字符串,而不是数组。 - szmegma
1
x=x.concat() in js means to clone a new array from x and return it to x. The reason to do it is not mutate the original input. In php, you may use array_slice($x, 0) to return a new array from it instead of array_push($x,$x); - ikhvjs
你的 JavaScript 函数无法运行,在第 21 行引用了一个不存在的 bs 变量。在第 21 行的 shift=bs-shift2 中,bs 应该是什么? - hanshenrik
还引用了一个不存在的函数,zeros 函数应该做什么? - hanshenrik
@hanshenrik 这只是整个文件的一小部分。bs=28(全局值),而zeros函数正在创建一个由零组成的数组。nmt = 3; q=zeros(nmt+1); 创建了这个 => q=[ 0, 0, 0, 0 ]。 - szmegma
4个回答

4
如果您使用x = x.concat()来确保您的函数不会修改原始传递的数组,那么在PHP版本的函数中,默认情况下参数将被复制而不是按引用传递,因此您不需要做任何事情来确保原始传递的数组不被修改。要强制以引用方式传递数组,在PHP中必须在参数名称前加上一个&符号(&)。可以通过以下程序演示这一点,其中我们定义了两个函数,每个函数都修改传递的数组的第一个元素。在第一个函数test1中,数组参数被复制,因此原始传递的数组保持不变。但是在第二个函数test2中,数组参数被按引用传递,并且当函数返回时,原始传递的数组将已被修改。这两个函数之间的唯一区别在于test1中参数被定义为$x,而在test2中被定义为&$x
<?php

// $x is passed by value:
function test1($x)
{
    $x[0] = 9;
    print_r($x);
}

// $x is passed by reference:
function test2(&$x)
{
    $x[0] = 9;
    print_r($x);
}

$my_array = [0, 1, 2];
echo "Pass by value:\n";
test1($my_array); //$my_array remains unmodified
print_r($my_array);

echo "\n\nPass by reference:\n";
test2($my_array); //$my_array is modified
print_r($my_array);

打印:

Pass by value:
Array
(
    [0] => 9
    [1] => 1
    [2] => 2
)
Array
(
    [0] => 0
    [1] => 1
    [2] => 2
)


Pass by reference:
Array
(
    [0] => 9
    [1] => 1
    [2] => 2
)
Array
(
    [0] => 9
    [1] => 1
    [2] => 2
)

更新

您有几个问题。首先,您描述了JavaScript和PHP版本中的toppart返回不同的值,并要求我们解释其中的差异,但您从未指定这些函数的实际startlenbx2输入是什么,并让我们自己去弄清楚。而且,您是否发布了toppart的PHP版本,因为我看不到。

其次,让我详细说明一下我的上一个回答。我应该说默认情况下数组将被复制而不是按引用传递(类对象按引用传递,但这在此处不适用)。但这不仅仅适用于将参数传递给函数。考虑以下内容:

$a = [0, 1, 2];
$b = $a; // copy-on-write
$b[0] = 9; // $b is [9, 1, 2]
echo $a; // $a is still [0, 1, 2]

如果您有一个JavaScript数组a和赋值操作b=a,这是一种引用复制,如果您修改b,则会修改由b引用的数组,也会修改由a引用的数组。在PHP中的语义等效于$b=@$a;,这确保了$b$a不仅引用同一个数组,而且如果您修改由$b引用的数组,则会修改相同的由$a引用的数组。

如果您定义了一个JavaScript函数如下:

function bdiv(x, y)
{
etc.

其中$x和$y是数组,理论上bdiv可以修改实际传递并别名化为xy的数组,因此在PHP中语义等效的做法是如下定义该函数:

function bdiv(@$x, @$y)
{
etc.

但是问题在于,随后在 JavaScript 代码中我们有:

x=x.concat()

在JavaScript中,您只能通过引用传递数组参数。早期的JavaScript代码中有:

        for(;;) {
            xx=bsub(x,y)
            if(xx.length==0) break
            x=xx; q++
        }
x的赋值语句会被有条件执行,但肯定会修改传递的x参数。所以当代码x=x.concat()被执行时,将当前x的副本分配给x,这肯定会阻止对传递数组的进一步修改,但它还能做什么呢?在复制x之前,有一些像arr['mod'] = x这样的赋值语句,并且在复制x之后,我们将其分配给x[0]。如果没有复制x,那么对x[0]的赋值也将更新存储在arr['mod']中的数组。因此,复制数组是为了防止出现这种情况。
有几种PHP习惯用语可以复制数组,如已经指出的$x = array_merge([], $x)。但是,即使在PHP中执行此语句,在将@$x定义为传递引用参数后,即仍将随后修改传递的数组,因为您进行了对由x引用的数组的赋值操作。因此,就传递的JavaScript x数组在函数返回时最终变成什么而言,就不存在与JavaScript结果完全相同的PHP等效项。下面的PHP程序表明,即使将x的副本分配给x,然后向由x引用的新数组进行赋值,它仍会更新传递的数组。
<?php

function test(&$x)
{
    $x = array_merge(array(), $x);
    $x[0] = 9;
    print_r($x);
}

$arr = [0, 1, 2];
test($arr);
print_r($arr);

打印输出:

Array
(
    [0] => 9
    [1] => 1
    [2] => 2
)
Array
(
    [0] => 9
    [1] => 1
    [2] => 2
)

因此,这种数组复制不会阻止对传递的数组进行进一步修改的影响。据我所知,原始的JavaScript程序实际上将原始的传递数组留在了未定义的状态中。您可能也可以将PHP函数定义为bdiv($x, $y),以便保留原始数组的未修改状态。

但是上面的讨论表明,您可能在其他地方遇到了问题。如果您有一个JavaScript赋值形式的赋值a = b,其中b是一个数组,并且存在后续的修改a,例如a[i] = some_value,相应的PHP代码必须是$a = @$b; etc,,也就是一个引用赋值。

如@AKX的评论所提到的,如果您遇到toppart的问题,您应该查看BC Math函数。


更清楚地说,PHP中的数组是通过引用传递的,但当数组发生变化时,它会复制该数组。关于这个问题有一个答案。https://dev59.com/8HI-5IYBdhLWcg3wED9V#9740541 - ikhvjs
1
@ikhvjs 谢谢 - 这是一个有趣的观点!所以这实际上是一种写时复制的情况。但它也可以说是一个(可能与版本相关的)实现细节,对于所有目的而言。 - Booboo
谢谢您的回复。您认为在PHP中我可以跳过JS部分吗?不需要 $x=array_merge(array(),$x); 如果我删除它,脚本的行为会有所不同。我已经在顶部更新了问题。 - szmegma
1
我重新评估了代码并更新了答案。我的原始答案很短视,只解决了修改原始传递数组的问题。但是看程序逻辑,我发现需要制作这个副本。但是在我的冗长更新中还有更多内容,请仔细阅读。 - Booboo

3

我很困惑你的JavaScript代码为什么会包含:

x=x.concat()
y=y.concat()

它们除了将同一数组分配给自身之外,没有任何作用。如果旨在避免更改原始数组,则可以使用以下代码替换这两行代码:

$x = array_merge(array(), $x);
$y = array_merge(array(), $y);

它们都具有完全相同的目的。

目前为止,我不确定你迄今编写的 PHP 代码的其余部分,但如果它能在任何方面帮助你,那就好。


到目前为止,这是最有效的解决方案:$x = array_merge(array(), $x); 对于$x,array_merge($x, $x)会创建错误的数组。 - szmegma

1

用array_merge替换array_push。

这将返回合并后的数组,然后将结果存储在$x中;

array_merge适用于数组。它将从一个数组中取出值并将其附加到另一个数组中。就像JS中的concat一样。

...将数组($x)拆分成多个值,这不是正确的输入。它相当于array_merge(1,2,3)(即没有输入数组)。

$x = array(1,2,3);
$x = array_merge($x,$x);
var_dump($x); //output: [1,2,3,1,2,3]

echo $x[4]; // output: 2

如果我误解了问题,请告诉我。


谢谢您的回复。看起来array_merge比array_push更好,但第一个输入必须是一个空数组,而不是$x本身=> $x = array_merge(array(),$x);现在脚本只是开始一个无限循环。我已经更新了问题。 - szmegma

1
使用array_merge代替array_push,例如:
  $x = array(1, 2, 3);
       print_r(array_merge($x,$x));

谢谢您的回复。看起来array_merge比array_push更好,但第一个输入必须是一个空数组,而不是$x本身=> $x = array_merge(array(),$x);现在脚本只是开始一个无限循环。我已经在顶部更新了问题。 - szmegma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接