How to compare floating-point values safely in TypeScript

2 Answers

0 votes
/*
====================================================================
 Title: Safe Floating-Point Comparison in TypeScript

 Purpose:
     Demonstrates how to compare floating-point values safely
     using absolute and relative tolerances instead of ===.

 Why not use === ?
     JavaScript/TypeScript numbers are represented using the
     IEEE 754 double-precision floating-point format.

     Many decimal values cannot be represented exactly in binary.

 Example:
     0.1 + 0.2 !== 0.3

     Tiny rounding errors may cause values that should be
     mathematically equal to differ slightly in memory.
====================================================================
*/

/*
 * Function: approximatelyEqual
 *
 * Safely compares two floating-point values.
 *
 * Parameters:
 *   a             - First value
 *   b             - Second value
 *   absTolerance  - Tolerance for values near zero
 *   relTolerance  - Tolerance scaled by magnitude
 *
 * Returns:
 *   true  -> Values are considered equal
 *   false -> Values are different
 *
 * Best Practice:
 *   Use both absolute and relative tolerances.
 *   This works well for small and large numbers.
 */
function approximatelyEqual(
    a: number,
    b: number,
    absTolerance: number = 1e-12,
    relTolerance: number = 1e-9
): boolean {

    // Calculate the absolute difference.
    const diff: number = Math.abs(a - b);

    /*
     * Compare against the larger of:
     *   1. The absolute tolerance
     *   2. The relative tolerance adjusted for scale
     */
    return diff <= Math.max(
        absTolerance,
        Math.max(Math.abs(a), Math.abs(b)) * relTolerance
    );
}

/*
 * Main Program
 */

// Example values that expose floating-point precision issues.
const x: number = 0.1 + 0.2;
const y: number = 0.3;

console.log("================================================");
console.log(" Safe Floating-Point Comparison in TypeScript");
console.log("================================================");
console.log("");

// Display high precision to reveal rounding differences.
console.log(`x = ${x.toPrecision(17)}`);
console.log(`y = ${y.toPrecision(17)}`);
console.log("");

/*
 * BAD PRACTICE:
 * Direct comparison using ===.
 *
 * Floating-point rounding errors may cause this
 * comparison to fail unexpectedly.
 */
if (x === y) {
    console.log("Direct comparison (===): Equal");
} else {
    console.log("Direct comparison (===): Not Equal");
}

console.log("");

/*
 * GOOD PRACTICE:
 * Compare using approximatelyEqual().
 */
if (approximatelyEqual(x, y)) {
    console.log("approximatelyEqual(): Equal");
} else {
    console.log("approximatelyEqual(): Not Equal");
}

console.log("");

/*
 * Additional test cases
 */
const largeA: number = 1000000.000000001;
const largeB: number = 1000000.000000002;

const nearZeroA: number = 0.000000000001;
const nearZeroB: number = 0;

console.log("Additional Tests:");
console.log("-----------------");

console.log(
    `Large numbers equal: ${approximatelyEqual(
        largeA,
        largeB
    )}`
);

console.log(
    `Near zero equal: ${approximatelyEqual(
        nearZeroA,
        nearZeroB
    )}`
);

console.log(
    `1.5 vs 2.0 equal: ${approximatelyEqual(
        1.5,
        2.0
    )}`
);



/*
run:

================================================
 Safe Floating-Point Comparison in TypeScript
================================================

x = 0.30000000000000004
y = 0.29999999999999999

Direct comparison (===): Not Equal

approximatelyEqual(): Equal

Additional Tests:
-----------------
Large numbers equal: true
Near zero equal: true
1.5 vs 2.0 equal: false

*/

 



answered 6 days ago by avibootz
0 votes
function approximatelyEqual(
    a: number,
    b: number,
    epsilon: number = 1e-9
): boolean {
    return Math.abs(a - b) <= epsilon;
}

/*
 * Main Program
 */

// Example values that expose floating-point precision issues.
const x: number = 0.1 + 0.2;
const y: number = 0.3;

console.log("================================================");
console.log(" Safe Floating-Point Comparison in TypeScript");
console.log("================================================");
console.log("");

// Display high precision to reveal rounding differences.
console.log(`x = ${x.toPrecision(17)}`);
console.log(`y = ${y.toPrecision(17)}`);
console.log("");

/*
 * BAD PRACTICE:
 * Direct comparison using ===.
 *
 * Floating-point rounding errors may cause this
 * comparison to fail unexpectedly.
 */
if (x === y) {
    console.log("Direct comparison (===): Equal");
} else {
    console.log("Direct comparison (===): Not Equal");
}

console.log("");

/*
 * GOOD PRACTICE:
 * Compare using approximatelyEqual().
 */
if (approximatelyEqual(x, y)) {
    console.log("approximatelyEqual(): Equal");
} else {
    console.log("approximatelyEqual(): Not Equal");
}

console.log("");

/*
 * Additional test cases
 */
const largeA: number = 1000000.000000001;
const largeB: number = 1000000.000000002;

const nearZeroA: number = 0.000000000001;
const nearZeroB: number = 0;

console.log("Additional Tests:");
console.log("-----------------");

console.log(
    `Large numbers equal: ${approximatelyEqual(
        largeA,
        largeB
    )}`
);

console.log(
    `Near zero equal: ${approximatelyEqual(
        nearZeroA,
        nearZeroB
    )}`
);

console.log(
    `1.5 vs 2.0 equal: ${approximatelyEqual(
        1.5,
        2.0
    )}`
);



/*
run:

================================================
 Safe Floating-Point Comparison in TypeScript
================================================

x = 0.30000000000000004
y = 0.29999999999999999

Direct comparison (===): Not Equal

approximatelyEqual(): Equal

Additional Tests:
-----------------
Large numbers equal: true
Near zero equal: true
1.5 vs 2.0 equal: false


*/

 



answered 6 days ago by avibootz
...