DavidDunham
DavidDunham

Reputation: 1362

Overwrite toFixed() with appropriate replacement to fix floating point error javascript

This is my attempt to fix the JavaScript toFixed() function...

Any input, ideas, corrections for possible errors are much appreciated!

This is my attempt -> Demo below (see console log)

enter image description here

Number.prototype.toFixed = function(fractionDigits) {
    var digits = parseInt(fractionDigits) || 0;
    var num = Number(this);
    if( isNaN(num) ) {
        return 'NaN';
    }
    
    var sign = num < 0 ? -1 : 1;
    if (sign < 0) { num = -num; }
    digits = Math.pow(10, digits);
    num *= digits;
    num = Math.round( Math.round(num * Math.pow(10,12)) / Math.pow(10,12) );
    var finalNumber = sign * num / digits;

    // add 0 after last decimal number (not 0) for as many as requested (fractionDigits)
    // in else case, check if requested digits exceed actual, then add 0 (avoid 10.1 for toFixed(2))

    if(fractionDigits > 0 && finalNumber.toString().indexOf('.') == -1){
        // check that .00 is present
        finalNumber = finalNumber.toString() + '.' + '0'.repeat(fractionDigits);
    } else if(fractionDigits > finalNumber.toString().split('.')[1]?.length){
        finalNumber = finalNumber.toString() + '0'.repeat((fractionDigits - finalNumber.toString().split('.')[1]?.length));
    }
    
    return finalNumber.toString(); // tofixed returns as string always, do the same
}

console.log('(35.355).toFixed(2)', (35.355).toFixed(2));
console.log('(35.1).toFixed(2)', (35.1).toFixed(2));
console.log('(35).toFixed(2)', (35).toFixed(2));

Number.prototype.toFixed = function(fractionDigits) {
//function toFixed(numberInput, fractionDigits){
    var digits = parseInt(fractionDigits) || 0;
    var num = Number(this);
    if( isNaN(num) ) {
        return 'NaN';
    }
    
    var sign = num < 0 ? -1 : 1;
    if (sign < 0) { num = -num; }
    digits = Math.pow(10, digits);
    num *= digits;
    num = Math.round( Math.round(num * Math.pow(10,12)) / Math.pow(10,12) );
    var finalNumber = sign * num / digits;

    // add 0 after last decimal number (not 0) for as many as requested (fractionDigits)

    if(fractionDigits > 0 && finalNumber.toString().indexOf('.') == -1){
        // check that .00 is present
        finalNumber = finalNumber.toString() + '.' + '0'.repeat(fractionDigits);
    } else if(fractionDigits > finalNumber.toString().split('.')[1]?.length){
        finalNumber = finalNumber.toString() + '0'.repeat((fractionDigits - finalNumber.toString().split('.')[1]?.length));
    }
    
    return finalNumber.toString(); // tofixed returns as string always, do the same
}

console.log('post-fix | (35.355).toFixed(2)', (35.355).toFixed(2));
console.log('post-fix | (35.1).toFixed(2)', (35.1).toFixed(2));
console.log('post-fix | (35).toFixed(2)', (35).toFixed(2));

Upvotes: 2

Views: 812

Answers (3)

Caribbean Coder
Caribbean Coder

Reputation: 11

function toFixed (varNumber, fixedDigits = 2) {
    let result = '', digit = '', remainder = 5; 
    let strNumber = (varNumber + '').replaceAll(/[^-.0-9]*/g, '');
    strNumber = strNumber.includes('.') ? strNumber : strNumber+ '.0'; 
    strNumber = strNumber + '0'.repeat(fixedDigits + 1);
    strNumber = strNumber.slice(0, strNumber.indexOf('.') + fixedDigits + 2);                
    for (let pos = strNumber.length - 1; pos >= 0; pos--) {
        digit = strNumber.charAt(pos);
        if (! '0123456789'.includes(digit)) {
            result = digit + result;
            continue;
        }
        result = (remainder + (+digit)) % 10 + result;
        remainder = remainder + (+digit) > 9 ? 1 : 0;            
    }
    return result.slice(0, (fixedDigits ? -1 : -2));
}

Upvotes: 1

NightEye
NightEye

Reputation: 11184

If it were me, I might have this string manipulation approach:

Number.prototype.toFixed = function(fractionDigits) {
  var number = String(this);
  var digits = fractionDigits || 0, length;
  
  if(digits < 0 && digits > 100) 
    throw 'RangeError: toFixed() digits argument must be between 0 and 100';

  var decimal = number.match(/(?<=\.)(\d*)/g);
  var factor = Math.pow(10, digits);
  if (decimal && decimal[0].length >= digits) 
    return String(Math.round(Number(number + '1') * factor) / factor);
  else {
    var length = digits - (decimal ? decimal[0].length : 0);
    var delimiter = number.includes('.') || !length ? '' : '.';
    return String(number) + delimiter + '0'.repeat(length);
  }
}

function test() {
  console.log((-35.555).toFixed(2))
  console.log((-35.35).toFixed(2))
  console.log((-35.9).toFixed(2))
  console.log((-35).toFixed(2))
}

Note:

  • I think you're not going to encounter a string in your toFixed since it will not be triggered by it so you don't need isNaN check.
  • Catch beforehand when the parameter is less than 0 or greater than 100. This should throw an error like the original one.

Output:

output

Upvotes: 1

MOR&#200;
MOR&#200;

Reputation: 2570

Instead of rounding number num = Math.round( Math.round(num * Math.pow(10,12)) / Math.pow(10,12) ); here you try parsing it to integer.

Math.round will round the value depending on its factorial part greater or less than 0.5. parseInt will simply fetch integer part without rounding, as you are expecting here.

console.log('(35.355).toFixed(2)', (35.355).toFixed(2));
console.log('(35.1).toFixed(2)', (35.1).toFixed(2));
console.log('(35).toFixed(2)', (35).toFixed(2));

Number.prototype.toFixed = function(fractionDigits) {
//function toFixed(numberInput, fractionDigits){
    debugger;
    var digits = parseInt(fractionDigits) || 0;
    var num = Number(this);
    if( isNaN(num) ) {
        return 'NaN';
    }
    
    var sign = num < 0 ? -1 : 1;
    if (sign < 0) { num = -num; }
    digits = Math.pow(10, digits);
    num *= digits;
    num = parseInt( Math.round(num * Math.pow(10,12)) / Math.pow(10,12) );
    var finalNumber = sign * num / digits;

    // add 0 after last decimal number (not 0) for as many as requested (fractionDigits)

    if(fractionDigits > 0 && finalNumber.toString().indexOf('.') == -1){
        // check that .00 is present
        finalNumber = finalNumber.toString() + '.' + '0'.repeat(fractionDigits);
    } else if(fractionDigits > finalNumber.toString().split('.')[1]?.length){
        finalNumber = finalNumber.toString() + '0'.repeat((fractionDigits - finalNumber.toString().split('.')[1]?.length));
    }
    
    return finalNumber.toString(); // tofixed returns as string always, do the same
}

console.log('post-fix | (35.355).toFixed(2)', (35.355).toFixed(2));
console.log('post-fix | (35.1).toFixed(2)', (35.1).toFixed(2));
console.log('post-fix | (35).toFixed(2)', (35).toFixed(2));

Upvotes: 1

Related Questions