在Javascript中比较十进制字符串

4

我有两个字符串的输入,每个字符串都代表一个非负有理数的小数格式。

给定这两个字符串 xy,我想检查由 x 表示的数值是否大于由 y 表示的数值。

这两个值可能非常大,并且可以扩展到非常高的精度(即小数点后面有许多位数字)。

因此,我不能依赖于 Number(x) > Number(y) 来完成工作。

相反,我已经实现了以下函数:

function isLarger(x, y) {
    const xParts = x.split(".").concat("");
    const yParts = y.split(".").concat("");
    xParts[0] = xParts[0].padStart(yParts[0].length, "0");
    yParts[0] = yParts[0].padStart(xParts[0].length, "0");
    xParts[1] = xParts[1].padEnd  (yParts[1].length, "0");
    yParts[1] = yParts[1].padEnd  (xParts[1].length, "0");
    return xParts.join("").localeCompare(yParts.join("")) == 1;
}

我已经进行了广泛的测试,通过生成随机输入xy,并将isLarger(x, y)的结果与表达式Number(x) > Number(y)的值进行比较。
当然,出于上述原因,我不能依赖于表达式Number(x) > Number(y)在每种可能的输入xy中都是正确的。
因此,我希望对上述函数进行某种反馈:
  • 它有任何注意事项吗?
  • 或许有更简单的方法来实现这个功能吗?

@NinaScholz:我已经多次使用xy都设置为(Math.random() * 1000000).toString()进行调用。然后将其输出与Number(x) > Number(y)进行比较。结果似乎总是成立的,但正如我所说 - 我怎么能相信Number(x) > Number(y)呢?此外,正如您所看到的,我的生成输入不包括极大值。 - goodvibration
我没有看到任何直接的注意事项。有一些库可以处理大数十进制,比如这个,但不知道它们在性能上与你的实现相比如何。 - Robby Cornelissen
@RobbyCornelissen:谢谢。我尝试避免使用BigNumber.jsDecimal.js,并保持其在“本地”的JS中。 - goodvibration
你肯定可以对你的实现进行一些优化。例如,只比较整数部分的长度可能会消除后期大量的填充和字符串比较。 - Robby Cornelissen
^并且您可以省略末尾填充。在拆分之前,您可以在字符串末尾添加一个点。顺便问一下,您是否喜欢.3这样的东西?或者在这种情况下,前导零是必需的吗? - Nina Scholz
显示剩余5条评论
2个回答

1

我在你的解决方案中没有看到任何警告,但是最后一行不必要地复杂。可以使用以下内容代替:

return xParts.join("") > yParts.join("");

简单的解决方案对输入值有一些限制

如果您能确保您的输入永远不会包含带有不必要的前导零的数字(例如001而不是1)或以点开始的小数(例如.1而不是0.1),则可以通过首先比较整数部分中的数字数量来显着简化比较。

整数部分中的更多数字意味着更大的数字,较少的数字意味着更小的数字。

当整数位数相同时,简单的字符串比较基本上就足够了。只需要进行一次填充操作-需要在y中填充尾随零,以便在x等于y但包含更多尾随零时不会得到真实结果(因为'211.00' > '211.0'true)。

function isLarger(x, y) {
    const xIntLength = x.search(/\.|$/); /* Finds position of . or end of string */
    const yIntLength = y.search(/\.|$/); /* Finds position of . or end of string */
    /* Compare lengths of int part */
    if (xIntLength !== yIntLength) {
      return xIntLength > yIntLength;
    } else {
    /* Add decimal point to Y if not present and add  trailing zeros
       because otherwise eg. '2.00' > '2.0' would return true */
      const yWithDecimalPoint = y.includes('.') ? y : y + '.';
      const paddedY = yWithDecimalPoint.padEnd(x.length,'0');
      return x > paddedY;
    }
}

谢谢。你确定 return xParts.join("") > yParts.join(""); 吗? - goodvibration
@goodvibration 是的,我对此非常确定。请参阅此描述,了解JS中字符串的简单比较。 (或者查看规范,如果您不信任随机来源。)localeCompare旨在特殊的区域设置感知字母顺序比较字符串(以确保例如á在其代码点值不相等的情况下出现在b之前)。 - Petr Srníček
@goodvibration 我已经修改了我的代码,以考虑y可能不包含小数点。(我的原始代码对于x=20.1y=20返回了错误的结果,因为y被填充为2000而不是20.0,而且'.'小于'0')。 - Petr Srníček
谢谢。我只采用了你的 xParts.join("") > yParts.join("") 建议,因为如果我的原始代码没有限制,那么我想尽可能保持简单(如有必要,以性能为代价)。谢谢。 - goodvibration

1
我对你的测试方法有一个警告。我根据问题和评论中理解的实现了它。为了测试这个测试方法,我在isLarger中引入了一个严重的错误,完全忽略了小数点后的部分。
即使有这样一个严重的错误,在1000万次运行中我得到了5个失败,相当于每百万次运行0.5个失败。
我认为你需要一个测试方法,其中包含更多数字部分匹配的情况。
这是我的程序:
function isLarger(x, y) {
    const xParts = x.split(".").concat("");
    const yParts = y.split(".").concat("");
    xParts[0] = xParts[0].padStart(yParts[0].length, "0");
    yParts[0] = yParts[0].padStart(xParts[0].length, "0");
    xParts[1] = xParts[1].padEnd(yParts[1].length, "0");
    yParts[1] = yParts[1].padEnd(xParts[1].length, "0");
    xParts[1] = "";
    yParts[1] = "";
    return xParts.join("").localeCompare(yParts.join("")) == 1;
}

function testIt(x, y) {
    const actual = isLarger(x, y);
    const expected = Number(x) > Number(y);
    if (actual != expected) {
        console.log("x=" + x + " y=" + y + " actual " + actual + " expected "
                + expected);
    }
}

function randAsString() {
    return (Math.random() * 1000000).toString();
}

testIt("3.3", "3.2");
for (var i = 0; i < 10000000; i++) {
    testIt(randAsString(), randAsString());
}

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