terça-feira, fevereiro 05, 2008

Precisão na soma de floats em javascript

Imagine-se que se quer somar dois valores que vêm de inputs, tendo por exemplo os valores 1,23 e 3,21.

var soma = parseFloat(input1) + parseFloat(input2);

O valor esperado na variável soma seria 4,44 infelizmente este não é o valor obtido, devido à precisão do float que não representa o número exactamente como lhe é passado, obtemos sim o valor 4,439999999. Para resolver este problema desenvolvi o seguinte algoritmo que possibilita truncar as casas decimais de um número.
//num = número a formatar; nDecimals = número de casas decimais
function Precision(num, nDecimals){
    var toReturn = "";
    num = "" + num;
    var pointIdx = num.indexOf('.');
    //garantir que é decimal
    if(pointIdx == -1){
        num += '.';
        pointIdx = num.indexOf('.');
    }
    var limit = pointIdx + nDecimals + 1;
    //quando se tem mais casas decimais que aquelas que se pretende
    if(num.length > limit){
        //arredondar para baixo
        if(num[limit] < '5')
        {
            for(var iter = 0; iter < limit; ++iter)
            {
                toReturn += num.charAt(iter);
            }
            return toReturn;
        }
        //arredondar para cima
        else{
            for(var iter = 0; iter < pointIdx + nDecimals; ++iter){
                toReturn += num.charAt(iter);
            }
            var lastNum = parseInt(num.charAt(pointIdx + nDecimals)) + parseInt(1);
            toReturn += lastNum;
            return toReturn;
        }
    }
    //acrescentar 0s à direita se necessário
    if(num.length < limit){
        while(num.length < limit){
            num += '0';
        }
    }
    return num;
}

4 comentários:

Anónimo disse...

Já que se está a lidar com números, para quê manipular strings quando se pode apelar ao Math?

function Precision( num, nDecimals ) {
    var i = Math.floor( num );
    var f = Math.round( ( num - i ) * Math.pow( 10, nDecimals) );
    return i + "." + f;
}

Tiago Sousa disse...

Tens razão uma abordagem tradicional é muito mais simples, o único problema aqui é que o cliente que encomendou isto é um chato de primeira em termos de formatação da string e não consegui manipular a formatação se não encarar o número como uma string no resto do código. Por exemplo um número normal seria 12345.8 o que eles querem que apareça é 12 345,80.
Para além disto ainda tenho de detectar a introdução de pontos ou caracteres não numéricos e remove-los por javascript, uma simples soma nem imaginas as dores de cabeça e as discussões que gerou!!!

Anónimo disse...

Strings SUCK!!! ;-)

Mas mesmo que não fossem preciso todas essas mariquiçes, a minha primeira solução também não dava totalmente conta do recado. Papa os zeros à esquerda da parte decimal.

function precision(n,d) {
  function zero_pad (n,l) {
    function zeros (n) { var z = ''; for ( i = 0; i < n; ++i ) z += '0'; return z; }
    var p = 0;
    for ( t = Math.pow(10,l)/10; t >= 1; t /= 10 ) {
      if ( 0 == Math.floor(n/t) ) p += 1;
      else break;
    }
    return zeros(p);
  }
  var i = Math.floor(n);
  var f = Math.round((n-i) * Math.pow(10,d));
  return i + "." + zero_pad(f,d) + (0 != f ? f : "");
}

Anónimo disse...

é mais facil que parece:

var stringue = (28.674635241).toFixed(2);

Resultado:
#String: 28.67