среда, 23 мая 2012 г.

Округление числа в js

Как это ни странно, но в js крайне ужасно реализованы функции округления числа. Например
var e = 79892.805;
alert(e.toFixed(2));
Думаете увидите 79892.81? Конечно все зависит от браузера, и может быть где-то что-то работает нормально.
Строго говоря проблема собственно не в округлении, а связана с хранением дробных чисел. Очень часто js почему-то решает что 0.99999999 эвивалентно 1. Хотя при сравнении ошибка тут же обнаруживается, т.е.  0.99999999 != 1.
Для решения своих мелких и не очень задач накидал функцию округления. Ниже приведен код с тестированием с помощью php:
<html>
<head>
</head>
<script type="text/javascript">

function roundTo (num, count) {
        var count = typeof count !== 'undefined' ? count : 2;
        var s = String(num);
        var e = s.indexOf('.');
 if( e == -1 )
  return num;
 var c = s.length - e - 1;
 if( c < count ) count = c;
 var e1 = e + 1 + count;
 var d = Number(s.substr(0,e) + s.substr(e+1, count));
        if( s[e1] > 4 ){
            d += 1;
        }
 d /= Math.pow(10, count);
        return d;
}

function roundTo_test(n, m, c){
 if(Number(roundTo(n,c)) != m){
  document.write(n + ' ' + roundTo(n,c) + ' != ' + m + '<br>');
  errors++;
 }
 tests++;
}

var tests = 0;
var errors = 0;
<?php
for($i=0; $i<10000; $i++){
 $n = rand(0, 9999999999) / 1000;
 $c = rand(0, 4);
 $m = round($n, 2);
 echo "roundTo_test({$n},{$m}, {$c});";
 //echo "if((Math.round({$n}*100)/100) != {$m}){ document.write('{$n} ' + (Math.round({$n}*100)/100) + ' != {$m}<br>'); errors++;}\n";
}
?>
document.write(tests + ' tests <br>' + errors + ' errors<br>');

</script>
<body>
<?php
?>
</body>
</html>

В тесте так же закоментирована строчка по тестированию функции round - можно убедится что ошибок допускает она не мало.
Для удобства можно функцию определить в прототип числа:
Number.prototype.roundTo = function( digitsCount ){
    var digitsCount = typeof digitsCount !== 'undefined' ? digitsCount : 2;
    var s = String(this);
    var e = s.indexOf('.');
    if( e == -1 ) return this;
    var c = s.length - e - 1;
    if( c < digitsCount ) digitsCount = c;
    var e1 = e + 1 + digitsCount;
    var d = Number(s.substr(0,e) + s.substr(e+1, digitsCount));
    if( s[e1] > 4 ) d += 1;
    d /= Math.pow(10, digitsCount);
    return d;
}

Math.roundTo = function( number ,digitsCount){
    number = Number(number);
    return number.roundTo(digitsCount);
}