如果我们采用奇妙的数学(即线性方程式)和同样奇妙的
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;
这两部分相等,这意味着一切并不是那么绝望,-24确实是一种解决方案。
搜索懒人
如果我们绘制函数
f1(x)=(8 -x)/ 4和
f2(x)= -x / 3的图 ,那么我们当然会找到
x = -24处两条线的唯一交点。

但是我们在左表达式中用按位运算做了几个不相等的替换,因此实际上函数
f1的图会稍有不同。 对于任何
x,函数
的值都将是与连续线
f1上的值不同的整数,并且可能在-1到1的范围内移动。这意味着我们可以将解的搜索区域限制在-24的左右,其中函数
f1和
f2的值开始相差一个以上。
要找到搜索区域的边界,您可以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);
因此,
x = [-24,-21,-18,-15]是第一个方程式的理想解。
图形解决方案
枚举当然是成功的,但是让我们仍然找出函数
f1的图形并最终以图形方式求解方程。 此外,该解决方案并不意味着必须拥有浏览器控制台的所有权。
按位NOT运算符仅舍弃小数部分,因此结果
-(x + 1)将被舍入。 位移运算符稍微复杂一点。 我们将其替换为整数除法,但是根据股息数的符号,此操作将结果向下或向上取整:
10 >> 2;
但是,我们知道所需的
x在[-36,-12]范围内。 因此,按位移位(
8 -x )的左操作数在[20、44]的范围内,即始终为正。 这又意味着整数除法舍入。
确定了操作的性质之后,我们可以绘制函数
f1的正确图形:

我们将在相同的坐标
x = [-24,-21,-18,-15]中找到函数的四个交点。
第二等式
因此,我们得出第二个方程:
9 +~ x >> 6 / 3 = -x / 3 | 0
它与第一个不同之处在于添加了按位OR。 如果此操作的右操作数为零,则结果就是左操作数的值,其中小数部分被丢弃。
首先,让我们进行相同的搜索,只需确定搜索区域即可。 由于现在函数
f2与
f1具有相似的特性,因此,出于可靠性考虑,在函数的绝对值相差两个以上单位(即[-48,0])的情况下,应汇总可能的移位并限制搜索。
let eq2 = x => 9 +~ x >> 6 / 3 == -x / 3 | 0; findx(eq2, -48, 0);
我们得到了相同的答案。 有人怀疑毕竟出了点问题。 但是事实是,将原始方程式表示为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);
解决方案的数量已经增加。 现在让我们看一下函数图。 与
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;
在等式的右侧,在按位运算之前,我们将
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);
如果您考虑一下,可以在该根旁边找到其余11个整数解的投影。
因此,出现了许多新的解决方案,并且只考虑了最接近的正数-24。 但是,这并不像主要任务那么有趣,并且在分析参与者的决策时,会分别评估非常罕见的答案。 为了避免混淆,您可以将对所需整数的限制(如带符号的32位整数)引入到问题条件中。
而且你做不到! 然后,要找到最小的根,应注意
Number.MAX_SAFE_INTEGER的附近带有负号,因为该数字是整数且具有极高的精度float64。 好吧,那就靠你自己了。
结论
作为会议的结果,大多数参与者通过详尽搜索解决了该问题,而搜索范围由于各种原因而完全不同。 但是,正如我们所看到的,足以以〜50个整数运行。 许多人陷入了业务优先陷阱。 有人还用图形确定了这一点。 单位对32个类别的发布感到惊讶。 您可以使用蛮力来进一步推进任务。 但是,为了获得额外的奖励,仍然有必要进行一些近似数学的分析。
我真的希望您喜欢这样的非典型任务,例如会议形式的娱乐。 在过去的一年中,我已经积累了许多这样的“有趣的” JavaScript任务。 我决定将它们全部收集在一个地方。 如果您不害怕,请
单击以下链接:
Unexpected Challenged JavaScript 。 会议还提出了
外观复杂和
管道破裂的任务。 是的,有很多这样的收藏,但这是我的收藏! 再次感谢大家。