有趣的JavaScript:几乎线性的方程式

如果我们采用奇妙的数学(即线性方程式)和同样奇妙的JavaScript ,然后彼此叠加,该怎么办? 在js环境的局限性和特定性的条件下,一个简单的数学问题可能会变成一个非常好奇且充满js石头陷阱的地方。 在上届在莫斯科举行的HolyJS 19会议上,一个这样的线性方程(包括SEMrush的其他任务)引起了不小的轰动



是的,这又是娱乐性JavaScript的标题:我请您削减所有关心的人。


当然,下面描述的所有内容-只是为了取乐而共生两个美好的事物的轻率尝试-不应被认真对待。 如果不是为了会议参加者的切身利益,就不会有这种材料,对此特别感谢!

措辞


1.找到方程的所有整数解:

9 +~ x >> 6 / 3 = -x / 3 

2.找到方程的所有整数解:

 9 +~ x >> 6 / 3 = -x / 3 | 0 

第二个方程式与第一个方程式的不同之处仅在于右侧的附加运算。

数学近似


我们来看第一个方程。 首先,我们将根据下表了解所使用操作的优先级:

 (9 +(~ x)) >> (6 / 3) = -x / 3 

我们从x取按位求反,然后将其加到9。相加的结果向右向右移位等于6除以3的结果的位数。

显然,问题在于对期望的x使用按位运算。 但是,为了找到一些条件根来进行进一步的推理,值得尝试将该方程式近似为数学模型。

按位运算将操作数用作带符号的32位整数。 可以用增量取整数取反来代替按位NOT:

 (9 -(x + 1)) >> (6 / 3) = -x / 3 (8 - x) >> 2 = -x / 3 

向右的按位移位(同时保留符号)可以用整数除以2来代替等于右操作数的程度:

 (8 - x) / 2^2 = -x / 3 (8 - x) / 4 = -x / 3 

当然,这些替换非常随意,我们将在后面讨论。 现在我们有了通常的线性方程,其唯一根是-24。 将值代入原始方程式的左侧和右侧:

 9 +~ (-24) >> 6 / 3; // = 8 -(-24) / 3; // = 8 

这两部分相等,这意味着一切并不是那么绝望,-24确实是一种解决方案。

搜索懒人


如果我们绘制函数f1(x)=(8 -x)/ 4f2(x)= -x / 3的图 ,那么我们当然会找到x = -24处两条线的唯一交点。



但是我们在左表达式中用按位运算做了几个不相等的替换,因此实际上函数f1的图会稍有不同。 对于任何x,函数值都将是与连续线f1上的值不同的整数,并且可能在-1到1的范围内移动。这意味着我们可以将解的搜索区域限制在-24的左右,其中函数f1f2的值开始相差一个以上。

要找到搜索区域的边界,您可以1)用模块解决不等式,或2)仔细查看函数图。 我们将发现x值得看一下段[-36,-12]:

 | (8 - x) / 4 + x / 3 | <= 1 



为了遍历某个封闭范围[beg,end]中的整个解决方案我们编写了findx函数:

 const findx = (f, beg, end) => [...Array(end - beg + 1)].map((_, i) => i + beg).filter(f); 

该函数返回一个整数数组,为其传递的函数f的值减小为true 。 为了找到解决方案,我们使用等式运算符将方程式表示为js函数:

 let eq1 = x => 9 +~ x >> 6 / 3 == -x / 3; findx(eq1, -36, -12); // [-24, -21, -18, -15] 

因此, x = [-24,-21,-18,-15]是第一个方程式的理想解。

图形解决方案


枚举当然是成功的,但是让我们仍然找出函数f1的图形并最终以图形方式求解方程。 此外,该解决方案并不意味着必须拥有浏览器控制台的所有权。

按位NOT运算符仅舍弃小数部分,因此结果-(x + 1)将被舍入。 位移运算符稍微复杂一点。 我们将其替换为整数除法,但是根据股息数的符号,此操作将结果向下或向上取整:

 10 >> 2; // = 2 -10 >> 2; // = -3 

但是,我们知道所需的x在[-36,-12]范围内。 因此,按位移位( 8 -x )的左操作数在[20、44]的范围内,即始终为正。 这又意味着整数除法舍入。

确定了操作的性质之后,我们可以绘制函数f1的正确图形:



我们将在相同的坐标x = [-24,-21,-18,-15]中找到函数的四个交点。

第二等式


因此,我们得出第二个方程:

 9 +~ x >> 6 / 3 = -x / 3 | 0 

它与第一个不同之处在于添加了按位OR。 如果此操作的右操作数为零,则结果就是左操作数的值,其中小数部分被丢弃。

首先,让我们进行相同的搜索,只需确定搜索区域即可。 由于现在函数f2f1具有相似的特性,因此,出于可靠性考虑,在函数的绝对值相差两个以上单位(即[-48,0])的情况下,应汇总可能的移位并限制搜索。

 let eq2 = x => 9 +~ x >> 6 / 3 == -x / 3 | 0; findx(eq2, -48, 0); // [-24, -21, -18, -15] 

我们得到了相同的答案。 有人怀疑毕竟出了点问题。 但是事实是,将原始方程式表示为js函数后,我们通过相等运算符将两个表达式(左和右)组合为一个表达式。 并且相等运算符具有其优先级,该优先级高于按位或的优先级。 该功能与以下功能相同:

 x => (9 +~ x >> 6 / 3 == -x / 3) | 0; 

在这种情况下,按位OR无效,因为true | 0 = 1 。 为避免这种情况,有必要在函数体中明确指出我们正在比较两个子表达式的结果:

 let eq2 = x => (9 +~ x >> 6 / 3) == (-x / 3 | 0); findx(eq2, -48, 0); // [-32, -29, -28, -26, -25, -24, -23, -22, -21, -19, -18, -15] 

解决方案的数量已经增加。 现在让我们看一下函数图。 与f1相似,“阶梯”构造了一个修改后的函数f2



在某些地方,函数图重叠,但是我们只对x坐标为整数的点感兴趣:[-32,-29,-28,-26,-25,-24,-23,-22,-21,-19, -18,-15],只有12个解决方案。 如果需要,可以通过算法找到两个“阶梯”与步骤3和4的交集。

附加问题


在会议上提出的问题中,还有一个问题:必须找到方程2的最小解。没有说这一定是整数,因此答案x = -32-原来是不正确的。

在这里无法通过蛮力找到解决方案,但是以图形方式解决它非常简单。 这是右边最接近-33的值x



似乎x = -32。(9)。 但这不是事实。 由于我们的环境是JavaScript,这意味着在数字表示中,我们受到所使用数据类型的限制。 类型号是float64, 一个双精度浮点数 (IEEE 754)。 记住这一点并命名近似精度就足以得到一只毛绒狐狸!

按位运算的黑暗面


如上所述,按位运算将操作数转换为32位数字,由序列0和1表示-这是范围[-2147483648、2147483647]。 如果数字不适合它,那么最高有效位将被丢弃。

在第一个等式中,这没有任何作用,因为在右侧没有按位运算。 但是在第二个方面,这种数字转换产生了有趣的效果。

例如,数字-24将表示为:

 11111111111111111111111111101000 

该数字的负值是通过将正数的记录中的位(按位非)相加(加1)而获得的。

在此32位序列结束的转换后,范围之外的任何数字在二进制运算中都将与数字-24相同。 例如,这些是数字:

 4294967272 | 0; // -24 8589934568 | 0; // -24, prepend '1' 12884901864 | 0; // -24, prepend '10' 17179869160 | 0; // -24, prepend '11' 21474836456 | 0; // -24, prepend '100' // ... 

在等式的右侧,在按位运算之前,我们将x除以3。我们在可以被3整除的“等价物” -24中找到x ,最接近的数字是12884901864。将其代入等式:

 9 +~ 12884901864 >> 6 / 3 = -12884901864 / 3 | 0 9 +~ 12884901864 >> 2 = -4294967288 | 0 9 + 23 >> 2 = 8 8 = 8 

除以3(-4294967288)的结果不适用于分配的32位数字; 反转位时,符号最终丢失,仅剩下8个:

 00000000000000000000000000001000 

此外,您可以通过调用eq2函数来验证结果的正确性:

 eq2(12884901864); // true 

如果您考虑一下,可以在该根旁边找到其余11个整数解的投影。

因此,出现了许多新的解决方案,并且只考虑了最接近的正数-24。 但是,这并不像主要任务那么有趣,并且在分析参与者的决策时,会分别评估非常罕见的答案。 为了避免混淆,您可以将对所需整数的限制(如带符号的32位整数)引入到问题条件中。

而且你做不到! 然后,要找到最小的根,应注意Number.MAX_SAFE_INTEGER的附近带有负号,因为该数字是整数且具有极高的精度float64。 好吧,那就靠你自己了。

结论


作为会议的结果,大多数参与者通过详尽搜索解决了该问题,而搜索范围由于各种原因而完全不同。 但是,正如我们所看到的,足以以〜50个整数运行。 许多人陷入了业务优先陷阱。 有人还用图形确定了这一点。 单位对32个类别的发布感到惊讶。 您可以使用蛮力来进一步推进任务。 但是,为了获得额外的奖励,仍然有必要进行一些近似数学的分析。

我真的希望您喜欢这样的非典型任务,例如会议形式的娱乐。 在过去的一年中,我已经积累了许多这样的“有趣的” JavaScript任务。 我决定将它们全部收集在一个地方。 如果您不害怕,请单击以下链接: Unexpected Challenged JavaScript 。 会议还提出了外观复杂管道破裂的任务。 是的,有很多这样的收藏,但这是我的收藏! 再次感谢大家。

Source: https://habr.com/ru/post/zh-CN475594/


All Articles