What are ways of comparing number values where some come in the form of e.g. '32k' or '2.5m'

99 Views Asked by At

Is there a standard way in JavaScript to easily compare two numbers where one or both can be in the form of e.g. '32k'. For example:

'32k' > 310
'2.5k' > 450
300 > 200
'2.5m' > '2.5k'

So that one easily could find the max value of an array like ... [300, '3k', '3.2k']. I could try to create my own function but I wondered if there is a standard way to do this?

2

There are 2 best solutions below

0
rbrundritt On BEST ANSWER

If you have 'k', or 'm', then the "number" is actually a string object, so you would need to parse out the number before you could do a comparison. Since you are only looking for the max value, a simple loop, parse, convert, and compare should be sufficient. Here is an example that supports 'k', 'm', 'b', 't' (case insensitive).

//Using a simple object for fast lookups of mutliplier.
const abvMap = {
  'k': 1000,
  'm': 1000000,
  'b': 1000000000,
  't': 1000000000000
};

function getMaxIndex(numArray) {
  if (!numArray || !Array.isArray(numArray) || numArray.length === 0) {
    throw 'invalid input';
  }

  //Start with the first number being the 
  let maxValue = parseNumber(numArray[0]);
  let maxIdx = 0;

  for (let i = 1, len = numArray.length; i < len; i++) {
    let num = parseNumber(numArray[i]);

    if (num > maxValue) {
      maxValue = num;
      maxIdx = i;
    }
  }

  return maxIdx;
}

function parseNumber(numString) {
  //Check to see if input is a number already. If so, return it.
  if (typeof numString === 'number') {
    return numString;
  }

  //Capture the number by removing any letters, $ sign (make it more reusable)
  let num = parseFloat(numString.replace(/[a-zA-Z$]/gi, ''));

  //Capture the number abbreviation. 
  let abv = numString.replace(/[-$\d.\s]*/gi, '');

  if (abv) {
    //Normalize abbreviation.
    abv = abv.toLowerCase();

    //Get the multiplier.
    let multiplier = abvMap[abv];

    if (multiplier) {
      //Multiply the value accordingly.
      return num * multiplier;
    }
  }

  return num;
}

function getMaxValue(numArray) {
  //Get the index of the max value, then return that value from the array.
  return numArray[getMaxIndex(numArray)];
}

//Test array of mixed number values.
var myArray = ['300', '3k', '3.2k', '0.0001k', '$15', '$30k', 1000];


document.body.innerHTML = getMaxValue(myArray);

1
Peter Seliger On

One possible approach for a straightforward implementation was ...

  • ... to provide a single, mostly regex and lookup based, parse functionality which computes number values from either real numbers or certain string-based number-formats.

  • ... to map an array of possible number representatives by creating each an object which contains the original value and the parsed value.

  • ... to finally sort the mapped array-items descending by each item's parsed value and assign the sorted array's first item's origin value as the maximum value.

The used regex ... /(?<value>\d+(?:\.\d+)?)(?<unit>[kmbt]?)/ ... utilizes named capturing groups for an input-value's number-value and numeral-unit part.

function parseEnUSNumeralValue(input) {
  // en-US based numeral abbreviation to multiplier lookup.
  const enUSNumeralLookup = {
    k: 1_000,
    m: 1_000_000,
    b: 1_000_000_000,
    t: 1_000_000_000_000,
  };
  const { value, unit } = (/(?<value>\d+(?:\.\d+)?)(?<unit>[kmbt]?)/)
    // see ... [https://regex101.com/r/xzlOqk/1]
    .exec(String(input))
    ?.groups ?? {}

  return (
    parseFloat(value) * (enUSNumeralLookup[unit] ?? 1)
  );
}
const numeralValues =
  ['300', '3k', '3.2k', '0.0001k', '$15', '$30k', 1000];

const [{ origin: max }] = numeralValues
  .map(value => ({
    origin: value,
    parsed: parseEnUSNumeralValue(value),
  }))
  .sort((a, b) => b.parsed - a.parsed)

// result ... original `max` value.
console.log({ max });

// in order to display the approach/implementation behind.
console.log(
  'mapped and descending sorted originally provided array ...',
  numeralValues
    .map(value => ({
      origin: value,
      parsed: parseEnUSNumeralValue(value),
    }))
    .sort((a, b) => b.parsed - a.parsed)
)
.as-console-wrapper { min-height: 100%!important; top: 0; }