0

I need to calculate the middle point (average) between two real numbers in JavaScript. The range of the numbers can vary widely, generally between 10000 and 0.0001.

The naive approach

(parseFloat(first) + parseFloat(second)) / 2

gives me unwanted precission errors, i.e.

(1.1 + 0.1) / 2 = 0.6000000000000001

How can I ensure that the result does not have extra decimal spaces? I guess, since there are two and only two inputs, that the result will need to have maximum one more decimal place than the inputs. So, I need:

 1000 and 3000 to return 2000 (without decimal spaces)
 1234.5678 and 2468.2468 to return 1851.4073
 0.001 and 0.0001 to return 0.00055
 10000 and 0.0001 to return 5000.00005
 0.1 and 1.1 to return 0.6

To clarify: I know all about precision errors and why this happens. What I need is a simple workaround, and I have not been able to find a previous solution on SO.

Cœur
  • 37,241
  • 25
  • 195
  • 267
SWeko
  • 30,434
  • 10
  • 71
  • 106
  • 2
    You need 1) to read a little about the basics of numbers in computing (mainly IEEE754) 2) to format your numbers at display. – Denys Séguret Jan 30 '13 at 16:07
  • Or even use google, or [SO]'s own search to find a gazillion questions of why this happens. I'm surprised you didn't use this approach with this high reputation. One thing I advise to try out: `alert(parseFloat("0.1"));` You'll be surprised... – ppeterka Jan 30 '13 at 16:10
  • @dystroy Knowing the specification does not really help me here – SWeko Jan 30 '13 at 16:10
  • @SWeko yes it will... Just try to write out 0.6 as a binary number. Then you will see what happens. In Java, that is why we use BigDecimal and the likes. Here is a [good question about this](http://stackoverflow.com/questions/661562/how-to-format-a-float-in-javascript). Particularly the toFixed is interesting – ppeterka Jan 30 '13 at 16:17
  • 1
    @ppeterka Ok, let me rephrase the question: where is Javascript's BigDecimal? – SWeko Jan 30 '13 at 16:18

5 Answers5

3

You'll want to use the function toFixed(). Here's what your code could look like ((1.1 + 0.1) / 2).toFixed(4);. And 4 is the number of decimal spaces.

Here's a sample

var num1 = "123.456";
var num2 = "456.1235";
if (num1.split('.')[1].length > num2.split('.')[1].length)
  var x = ((parseFloat(num1) + parseFloat(num2)) / 2).toFixed(num1.split('.')[1].length+1);
else
  var x = ((parseFloat(num1) + parseFloat(num2)) / 2).toFixed(num2.split('.')[1].length+1);

And remember inputs are strings, so that's why num1 and num2 are strings.

Update: Here's the correct if statements for your desired results.

var num1 = "123.456";
var num2 = "456.1235";
if (num1.split('.').length == 1) {
  if (num2.split('.').length == 1)
    var x = ((parseFloat(num1) + parseFloat(num2)) / 2).toFixed(0);
  else
    var x = ((parseFloat(num1) + parseFloat(num2)) / 2).toFixed(num2.split('.')[1].length);
} else if (num2.split('.').length == 1)
  var x = ((parseFloat(num1) + parseFloat(num2)) / 2).toFixed(num2.split('.')[1].length);
else if (num1.split('.')[1].length == num2.split('.')[1].length)
  var x = ((parseFloat(num1) + parseFloat(num2)) / 2).toFixed(num2.split('.')[1].length);
else if (num1.split('.')[1].length > num2.split('.')[1].length)
  var x = ((parseFloat(num1) + parseFloat(num2)) / 2).toFixed(num1.split('.')[1].length+1);
else if (num1.split('.')[1].length < num2.split('.')[1].length)
  var x = ((parseFloat(num1) + parseFloat(num2)) / 2).toFixed(num2.split('.')[1].length+1);

EDIT I had to change the order it checked for numbers with no decimal space. Here's a jsFiddle I created to help with the testing: link

Graham Walters
  • 2,054
  • 2
  • 19
  • 30
  • does not work for me, because I might need no decimal spaces, or more then 4 decimal spaces. Will try to lop off the trailing zeros, and see how that works out. – SWeko Jan 30 '13 at 16:28
  • You could easily add to the if statements to get your desired result. – Graham Walters Jan 30 '13 at 16:35
  • actually something like this might do the trick. Now I'm not sure what the average of `1.001` and `0.999` should be displayed as :) – SWeko Jan 30 '13 at 16:40
  • I've added the additional if statements to achieve your desired result :) – Graham Walters Jan 30 '13 at 16:51
2

Numbers in Javascript have the toFixed(n) method which removes all but n decimal places. This, however, isn't very helpful when you don't know if you have a very large or very small number. But what you can do is first take Math.floor() of the logarithm to base 10 of the number to get the number of decimal places in front of the dot, divide the number by 10^places, perform toFixed(n) where n is the maximum number of digits, and then multiply it again with 10^places.

Philipp
  • 67,764
  • 9
  • 118
  • 153
1

Numbers in javascript are all double precision floats as defined by IEEE754. This means there isn't a dot position defined in base 10 and that you'll always find those cases where the precision doesn't seem to be what you want.

The solution is either

  • to not use js numbers to do your computation. This solution is heavy and generally useless.
  • to format your numbers into strings when you need to display them (and only at that time). You may use to Fixed or toPrecision.

Useful read : What Every Computer Scientist Should Know About Floating-Point Arithmetic

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
1

You can use the 'precision' of the numbers in use to judge the appropriate precision of the return value.

This example averages any number of numbers to the greatest precision of its arguments.

Math.average= function(){
    var a= arguments, L= a.length, i= 0,
    total= 0, next, prec= [];
    if(L== 1) return +a[0];
    while(i<L){
        next= Number(a[i++]);
        total+= next;
        if(next!== Math.floor(next)){
            prec.push(String(next).split('.')[1].length);
        }
    }
    prec= Math.max.apply(Math, prec)+1;
    return +((total/L).toFixed(prec));
    // returns number after precision adjusted
}

/*

tests:
var n1= ;
Math.average(1.1, .01);
0.555

var n1= 1.0111, n2= .01;
Math.average(1.0111,  .01);
0.51055

var n1= 1.1, n2= .01, n3= 1, n4= 1.025;
Math.average(n1, n2, n3, n4)
0.7838

*/

kennebec
  • 102,654
  • 32
  • 106
  • 127
0

You can use Number.toPrecision(digits) to do that.

Philipp
  • 67,764
  • 9
  • 118
  • 153