Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compare decimal strings in Javascript

I have an input of two strings, each of which representing a non-negative rational number in decimal format.

Given these two strings x and y, I would like to check if the numeric value represented by x is larger than the numeric value represented by y.

These two values can be very large, as well as to extend to a very high precision (i.e., many digits after the decimal-point).

As such, I cannot rely on Number(x) > Number(y) to do the job.

Instead, I have implemented the following function:

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;
}

I have conducted extensive testing, by generating random input x and y, and comparing the result of isLarger(x, y) to the value of the expression Number(x) > Number(y).

Of course, for the reasons mentioned above, I cannot rely on the value of the expression Number(x) > Number(y) to be correct for every possible input x and y.

So I would like some kind of feedback on the function above:

  • Does it have any caveats in it?
  • Is there perhaps a simpler way of achieving this?
like image 428
goodvibration Avatar asked Sep 06 '25 02:09

goodvibration


2 Answers

I don't see any caveats in your solution, but the last line is unnecessarily complex. Instead of

return xParts.join("").localeCompare(yParts.join("")) == 1;

You could simply use this:

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

Simpler solution with some limitations on input values

If you can be absolutely sure that your input will never contain numbers with unecessary leading zeros (eg. 001 instead of 1) or decimal numbers starting with a point (eg. .1 instead of 0.1) you can signficantly simplify the comparison by first comparing the number of digits in the integer parts.

More digits in the integer part means a greater number, less digits means a smaller number.

When the number of integer digits is the same, a simple string comparison is basically sufficient. Only one padding operation is necessary - y needs to be padded with trailing zeros in order not to get truthy result if x equals y but contains more trailing zeros (because '211.00' > '211.0' is 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;
    }
}
like image 97
Petr Srníček Avatar answered Sep 07 '25 16:09

Petr Srníček


I have a caveat about your testing method. I implemented it as far as I understood from the question and comments. To test the test method, I introduced a gross error in isLarger, completely ignoring the portion after the decimal point.

Even with such a gross error, I got 5 failures in 10,000,000 runs, equivalent to 0.5 failures per million runs.

I think you need a test method that puts in more cases where parts of the numbers match.

Here is my program:

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());
}
like image 43
Patricia Shanahan Avatar answered Sep 07 '25 14:09

Patricia Shanahan