All files / utils/src/utilities/math isEqual.ts

72.41% Statements 21/29
64.1% Branches 25/39
70% Functions 7/10
71.42% Lines 20/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112128x             42918803x               67162x       67162x 159643x 34523x       32639x       85585482x       134324x                                                   42826322x       42826322x 42759160x     67162x 67162x           128x 228x   128x           128x       57x           128x                                    
export const DEFAULT_EPSILON = 1e-5;
 
export function areNumbersEqualWithTolerance(
  num1: number,
  num2: number,
  tolerance: number
): boolean {
  return Math.abs(num1 - num2) <= tolerance;
}
 
function areArraysEqual(
  arr1: ArrayLike<number>,
  arr2: ArrayLike<number>,
  tolerance = DEFAULT_EPSILON
): boolean {
  Iif (arr1.length !== arr2.length) {
    return false;
  }
 
  for (let i = 0; i < arr1.length; i++) {
    if (!areNumbersEqualWithTolerance(arr1[i], arr2[i], tolerance)) {
      return false;
    }
  }
 
  return true;
}
 
function isNumberType(value: unknown): value is number {
  return typeof value === 'number';
}
 
function isNumberArrayLike(value: unknown): value is ArrayLike<number> {
  return (
    value &&
    typeof value === 'object' &&
    'length' in value &&
    typeof (value as ArrayLike<number>).length === 'number' &&
    (value as ArrayLike<number>).length > 0 &&
    typeof (value as ArrayLike<number>)[0] === 'number'
  );
}
 
/**
 * Returns whether two values are equal or not, based on epsilon comparison.
 * For array comparison, it does NOT strictly compare them but only compare its values.
 * It can compare array of numbers and also typed array. Otherwise it will just return false.
 *
 * @param v1 - The first value to compare
 * @param v2 - The second value to compare
 * @param tolerance - The acceptable tolerance, the default is 0.00001
 *
 * @returns True if the two values are within the tolerance levels.
 */
export function isEqual<ValueType>(
  v1: ValueType,
  v2: ValueType,
  tolerance = DEFAULT_EPSILON
): boolean {
  Iif (typeof v1 !== typeof v2 || v1 === null || v2 === null) {
    return false;
  }
 
  if (isNumberType(v1) && isNumberType(v2)) {
    return areNumbersEqualWithTolerance(v1, v2, tolerance);
  }
 
  Eif (isNumberArrayLike(v1) && isNumberArrayLike(v2)) {
    return areArraysEqual(v1, v2, tolerance);
  }
 
  return false;
}
 
const negative = (v) =>
  typeof v === 'number' ? -v : v?.map ? v.map(negative) : !v;
 
const abs = (v) =>
  typeof v === 'number' ? Math.abs(v) : v?.map ? v.map(abs) : v;
 
/**
 * Compare negative values of both single numbers and vectors.
 */
export const isEqualNegative = <ValueType>(
  v1: ValueType,
  v2: ValueType,
  tolerance = undefined
) => isEqual(v1, negative(v2) as unknown as ValueType, tolerance);
 
/**
 * Compare absolute values for single numbers and vectors.
 * Not recommended for large vectors as this creates a copy.
 */
export const isEqualAbs = <ValueType>(
  v1: ValueType,
  v2: ValueType,
  tolerance = undefined
) => isEqual(abs(v1), abs(v2) as unknown as ValueType, tolerance);
 
/**
 * @param n - array of numbers or a simple number
 * @returns True if n or the first element of n is finite and not NaN
 */
export function isNumber(n: number[] | number): boolean {
  if (Array.isArray(n)) {
    return isNumber(n[0]);
  }
  return isFinite(n) && !isNaN(n);
}
 
export default isEqual;