How do you round a number in JavaScript in a base other than 10?

Viewed 301

For example, let's say I have the number 12.24 in base 8 (10.3125 in base 10), and I want to round the number to 1 point of precision so that I get 12.3.

1 Answers

Solution 1

  • There isn´t a function in js that can do this. So need to create one in order to do it. What it does is to check if the digit to fix (in your case the "4") is closer to the base (8) or closer to 0:

    if (base - digit_tofix <= base / 2) {
    

Then, just add or substract the difference to 10 or 0, so if the number is 12.24, it would return:

return 12.24 + 0.06;   // ---> equals to 12.3

If the number is 12.23, returns:

return 12.23 - 0.03    // ---> equals to 12.2

So could make something like this:

function round(n, base, precision) {
  const digit_tofix = n.toString().split(".")[1].split("")[precision];
  if (base - digit_tofix <= base / 2) {
    return (n + (10 - digit_tofix) / Math.pow(10, precision + 1)).toFixed(precision);
  }else {
    return (n - digit_tofix / Math.pow(10, precision + 1)).toFixed(precision);
  }
}

EDIT: however, this wouldn´t catch carry overflow. So a number in base_8 like this:

55.74

would transform in:

55.74 + 0.06 = 55.8   // --> number 8 doesn´t exist in octal

So there must be an overflow-check loop using do while(). What this statement do is to move the digit_tofix to the previous number and check if there is an overflow every time we make an addition:

// Overflow check
do {
    n += (10 - digit_tofix) / Math.pow(10, precision + 1);
    precision--;
    digit_tofix = precision > -1 ? n.toFixed(precision + 1).split(".")[1].split("")[precision] :
                                   n.toFixed(0).split(".")[0].split("")[precision + n.toString().split(".")[0].split("").length];
} while(digit_tofix == base);
    
return n.toFixed(precision > -1 ? precision + 1 : 0);
  • It works for any base up to 10 (not hexadecimal since contains letters) and any precision you want, just changing the parameters in the round() function. Here some working examples

Octal

const oct = 12.24;

console.log( round(oct, 8, 1) );

function round(n, base, precision) {
  let digit_tofix = n.toString().split(".")[1].split("")[precision];
  if (base - digit_tofix <= base / 2) {
    // Overflow check
    do {
        n += (10 - digit_tofix) / Math.pow(10, precision + 1);
        precision--;
        digit_tofix = precision > -1 ? n.toFixed(precision + 1).split(".")[1].split("")[precision] :
                                       n.toFixed(0).split(".")[0].split("")[precision + n.toString().split(".")[0].split("").length];
    } while(digit_tofix == base);
    return n.toFixed(precision > -1 ? precision + 1 : 0);
  }else {
    return (n - digit_tofix / Math.pow(10, precision + 1)).toFixed(precision);
  }
}

Binary

const bin = 10011.011;

console.log( round(bin, 2, 1) );

function round(n, base, precision) {
  let digit_tofix = n.toString().split(".")[1].split("")[precision];
  if (base - digit_tofix <= base / 2) {
    // Overflow check
    do {
        n += (10 - digit_tofix) / Math.pow(10, precision + 1);
        precision--;
        digit_tofix = precision > -1 ? n.toFixed(precision + 1).split(".")[1].split("")[precision] :
                                       n.toFixed(0).split(".")[0].split("")[precision + n.toString().split(".")[0].split("").length];
    } while(digit_tofix == base);
    return n.toFixed(precision > -1 ? precision + 1 : 0);
  }else {
    return (n - digit_tofix / Math.pow(10, precision + 1)).toFixed(precision);
  }
}


Solution 2

function round(n, base, precision) {
  // To get number value of letter: a = 10, b = 11, c = 12 ...
  const getNumber = e => isNaN(e) ? e.charCodeAt(0) - 87 : parseInt(e);

  // Just to check incompability and invalid parameters
  const checkIncompatible = () => {
    if (digits.reduce((a, d) => d == "." ? ++a : a, 0) > 1 || 
        (digits.some(d => isNaN(d) && (getNumber(d) < 10 || getNumber(d) > 35) && d != ".")))   return "Invalid Number";
    if (digits.some(d => getNumber(d) >= base  && d != "."))    return "Number doesn´t match base";
    if (precision < 0)  return "Invalid precision argument";
    if (base < 2 || base > 36)  return "Invalid base argument";
    return false;
  };
  
  // Recursive function to carry overflow
  const add = p => {
    if (digits[p] == ".") add(p - 1);
    else if (getNumber(digits[p]) + 1 == base) {
      digits[p] = "0";
      if (p > 0)    add(p - 1);
      else digits.unshift("1");
    }else {
      if (isNaN(digits[p])) digits[p] = String.fromCharCode(digits[p].charCodeAt(0) + 1);
      else digits[p] = digits[p] < 9 ? parseInt(digits[p]) + 1 : "a" ;
    }
  };
  
  n = n.toString().toLowerCase().split(",").join(".");  // In case there is a comma
  let digits = n.split("");

  // Avoid problems with undefined array index and other issues
  if (typeof digits[digits.indexOf(".") + 1 + precision] === "undefined" ||
      digits.reduce((a, d) => d == "." ? ++a : a, 0) < 1)   return n;
  let state = checkIncompatible();
  if (state) return state;

  const digit_tofix = getNumber(digits[digits.indexOf(".") + 1 + precision]);

  // Check if digit_tofix is closer to base or zero
  if (base - digit_tofix <= base / 2) {
   add(digits.indexOf(".") + precision);
  }

  // Splice the array to get the substring
  digits.splice(digits.indexOf(".") + 1 + precision);
  if (!precision) digits.splice(-1);
  return digits.join("");
}

This solution is more complex, but is broader since you can use any number from base 2 to 36, it means you can use hexadecimal.

Similarly to first solution, it checks if the digit to fix (in your case the "4") is closer to the base (8) or closer to 0:

if (base - digit_tofix <= base / 2) {

If is closer to zero, just need to take the substring of the number (using splice() array method). So if number is:

"12.23"

its substring is

"12.2"

But if digit-to-fix is closer to the base, we need to add +1 to the previous number, so if number is:

"12.24"

its substring is

"12.3"    //--> 2 + 1 = 3

Special cases

For example, if number in base_8 is:

"12.74"

its substring should be

"13.0"    // 7 + 1 = 8 (base is 8 so there is an overflow)

That´s why there is a recursive function called add() in order to carry in case of overflow.

The round() function returns a string with the new number.

Try the solution

Hexadecimal

console.log( round("c3.bf9", 16, 2) );
console.log( round("AA.D1", 16, 1) );
console.log( round("ff.fff", 16, 0) );

function round(n, base, precision) {
    const getNumber = e => isNaN(e) ? e.charCodeAt(0) - 87 : parseInt(e);
  const checkIncompatible = () => {
    if (digits.reduce((a, d) => d == "." ? ++a : a, 0) > 1 || 
        (digits.some(d => isNaN(d) && (getNumber(d) < 10 || getNumber(d) > 35) && d != ".")))   return "Invalid Number";
    if (digits.some(d => getNumber(d) >= base  && d != "."))    return "Number doesn´t match base";
    if (precision < 0)  return "Invalid precision argument";
    if (base < 2 || base > 36)  return "Invalid base argument";
    return false;
  };
  
  const add = p => {
    if (digits[p] == ".") add(p - 1);
    else if (getNumber(digits[p]) + 1 == base) {
      digits[p] = "0";
      if (p > 0)    add(p - 1);
      else digits.unshift("1");
    }else {
      if (isNaN(digits[p])) digits[p] = String.fromCharCode(digits[p].charCodeAt(0) + 1);
      else digits[p] = digits[p] < 9 ? parseInt(digits[p]) + 1 : "a" ;
    }
  };
  
  n = n.toString().toLowerCase().split(",").join(".");
  let digits = n.split("");
  if (typeof digits[digits.indexOf(".") + 1 + precision] === "undefined" ||
      digits.reduce((a, d) => d == "." ? ++a : a, 0) < 1)   return n;
  let state = checkIncompatible();
  if (state) return state;
  const digit_tofix = getNumber(digits[digits.indexOf(".") + 1 + precision]);
  if (base - digit_tofix <= base / 2) {
   add(digits.indexOf(".") + precision);
  }
  digits.splice(digits.indexOf(".") + 1 + precision);
  if (!precision) digits.splice(-1);
  return digits.join("");
}

Octal

console.log( round(12.24, 8, 1) );
console.log( round(17.77, 8, 1) );
console.log( round(0.74, 8, 1) );

function round(n, base, precision) {
    const getNumber = e => isNaN(e) ? e.charCodeAt(0) - 87 : parseInt(e);
  const checkIncompatible = () => {
    if (digits.reduce((a, d) => d == "." ? ++a : a, 0) > 1 || 
        (digits.some(d => isNaN(d) && (getNumber(d) < 10 || getNumber(d) > 35) && d != ".")))   return "Invalid Number";
    if (digits.some(d => getNumber(d) >= base  && d != "."))    return "Number doesn´t match base";
    if (precision < 0)  return "Invalid precision argument";
    if (base < 2 || base > 36)  return "Invalid base argument";
    return false;
  };
  
  const add = p => {
    if (digits[p] == ".") add(p - 1);
    else if (getNumber(digits[p]) + 1 == base) {
      digits[p] = "0";
      if (p > 0)    add(p - 1);
      else digits.unshift("1");
    }else {
      if (isNaN(digits[p])) digits[p] = String.fromCharCode(digits[p].charCodeAt(0) + 1);
      else digits[p] = digits[p] < 9 ? parseInt(digits[p]) + 1 : "a" ;
    }
  };
  
  n = n.toString().toLowerCase().split(",").join(".");
  let digits = n.split("");
  if (typeof digits[digits.indexOf(".") + 1 + precision] === "undefined" ||
      digits.reduce((a, d) => d == "." ? ++a : a, 0) < 1)   return n;
  let state = checkIncompatible();
  if (state) return state;
  const digit_tofix = getNumber(digits[digits.indexOf(".") + 1 + precision]);
  if (base - digit_tofix <= base / 2) {
   add(digits.indexOf(".") + precision);
  }
  digits.splice(digits.indexOf(".") + 1 + precision);
  if (!precision) digits.splice(-1);
  return digits.join("");
}

Binary

console.log( round(101.10, 2, 1) );
console.log( round("100,11", 2, 1) );
console.log( round(100.11, 2, 1) );

function round(n, base, precision) {
    const getNumber = e => isNaN(e) ? e.charCodeAt(0) - 87 : parseInt(e);
  const checkIncompatible = () => {
    if (digits.reduce((a, d) => d == "." ? ++a : a, 0) > 1 || 
        (digits.some(d => isNaN(d) && (getNumber(d) < 10 || getNumber(d) > 35) && d != ".")))   return "Invalid Number";
    if (digits.some(d => getNumber(d) >= base  && d != "."))    return "Number doesn´t match base";
    if (precision < 0)  return "Invalid precision argument";
    if (base < 2 || base > 36)  return "Invalid base argument";
    return false;
  };
  
  const add = p => {
    if (digits[p] == ".") add(p - 1);
    else if (getNumber(digits[p]) + 1 == base) {
      digits[p] = "0";
      if (p > 0)    add(p - 1);
      else digits.unshift("1");
    }else {
      if (isNaN(digits[p])) digits[p] = String.fromCharCode(digits[p].charCodeAt(0) + 1);
      else digits[p] = digits[p] < 9 ? parseInt(digits[p]) + 1 : "a" ;
    }
  };
  
  n = n.toString().toLowerCase().split(",").join(".");
  let digits = n.split("");
  if (typeof digits[digits.indexOf(".") + 1 + precision] === "undefined" ||
      digits.reduce((a, d) => d == "." ? ++a : a, 0) < 1)   return n;
  let state = checkIncompatible();
  if (state) return state;
  const digit_tofix = getNumber(digits[digits.indexOf(".") + 1 + precision]);
  if (base - digit_tofix <= base / 2) {
   add(digits.indexOf(".") + precision);
  }
  digits.splice(digits.indexOf(".") + 1 + precision);
  if (!precision) digits.splice(-1);
  return digits.join("");
}

Related