Math.round (num) vs num.toFixed (0) e inconsistências do navegador

Considere o seguinte código:

for (var i=0;i<3;i++){ var num = i + 0.50; var output = num + " " + Math.round(num) + " " + num.toFixed(0); alert(output); } 

No Opera 9.63 eu recebo:

0,5 1 0

1,5 2 2

2,5 3 2

No FF 3.03 eu recebo:

0,5 1 1

1,5 2 2

2,5 3 3

No IE 7 eu recebo:

0,5 1 0

1,5 2 2

2,5 3 3

Observe os resultados em negrito. Por que essas inconsistências estão presentes? Isso significa que toFixed(0) deve ser evitado? Qual é a maneira correta de arredondar um número para o inteiro mais próximo?

Edit: Para responder a sua edição, use Math.round . Você também pode prototipar o object Number para que ele faça seus lances se você preferir essa syntax.

 Number.prototype.round = function() { return Math.round(this); } var num = 3.5; alert(num.round()) 

Eu nunca usei Number.toFixed() antes (principalmente porque a maioria das bibliotecas JS fornece um método toInt() ), mas a julgar pelos seus resultados, eu diria que seria mais consistente usar os methods Math ( round , floor , ceil ) em seguida, para toFixed se a consistência entre navegadores for o que você está procurando.

Eu acho que FF está fazendo a coisa certa com toFixed, já que a etapa 10 abaixo diz “Se houver dois desses n, escolha o maior n”.

E como Grant Wagner disse: Use Math.ceil (x) ou Math.floor (x) em vez de x.toFixed () .

Tudo abaixo é da especificação da linguagem ECMAScript :

15.7.4.5 Number.prototype.toFixed (fractionDigits)

Retorna uma string contendo o número representado na notação de ponto fixo com dígitos fractionDigits após o ponto decimal. Se fractionDigits for indefinido, 0 será assumido. Especificamente, execute as seguintes etapas:

  1. Seja f ToInteger(fractionDigits) . (Se fractionDigits for indefinido, esta etapa produzirá o valor 0 ).
  2. Se f < 0 ou f > 20 , lance uma exceção RangeError .
  3. Seja x esse valor numérico.
  4. Se x é NaN , retorne a string "NaN" .
  5. Vamos ser a string vazia.
  6. Se x ≥ 0 , vá para o passo 9.
  7. Vamos ser "-" .
  8. Deixe x = –x .
  9. Se x ≥ 10^21 , deixe m = ToString(x) e vá para o passo 20.
  10. Seja n um inteiro para o qual o valor matemático exato de n ÷ 10^f – x seja o mais próximo de zero possível. Se houver dois desses n , escolha o n maior.
  11. Se n = 0 , seja m a string "0" . Caso contrário, seja a string que consiste nos dígitos da representação decimal de n (em ordem, sem zeros à esquerda).
  12. Se f = 0 , vá para o passo 20.
  13. Seja k o número de caracteres em m .
  14. Se k > f , vá para o passo 18.
  15. Seja z a string que consiste f+1–k ocorrências f+1–k do caractere '0' .
  16. Seja m a concatenação das cordas z e m .
  17. Deixe k = f + 1 .
  18. Seja a os primeiros k–f caracteres de m e seja b os demais f caracteres de m .
  19. Seja a concatenação das três cordas a "." e b .
  20. Retornar a concatenação das strings s e m .

A propriedade length do método toFixed é 1 .

Se o método toFixed for chamado com mais de um argumento, o comportamento será indefinido (consulte a seção 15).

Uma implementação tem permissão para estender o comportamento de toFixed para valores de fractionDigits menores que 0 ou maiores que 20 . Neste caso, toFixed não necessariamente lançaria RangeError para tais valores.

OBSERVAÇÃO A saída de toFixed pode ser mais precisa que toString para alguns valores, pois toString só imprime dígitos significativos suficientes para distinguir o número dos valores numéricos adjacentes. Por exemplo, (1000000000000000128).toString() retorna "1000000000000000100" , enquanto (1000000000000000128).toFixed(0) retorna "1000000000000000128" .

Para resolver seus dois problemas / perguntas originais :

Math.round (num) vs num.toFixed (0)

A questão aqui reside no equívoco de que estes devem sempre dar o mesmo resultado. Eles são, de fato, governados por regras diferentes. Veja números negativos, por exemplo. Como o Math.round usa “round half up” como regra, você verá que Math.round(-1.5) avaliado como -1 mesmo que Math.round(1.5) seja 2 .

Number.prototype.toFixed , por outro lado, usa o que é basicamente equivalente a “round half away from zero” como regra, de acordo com o passo 6 da especificação , que essencialmente diz tratar os negativos como números positivos, e então adicionar de volta o sinal negativo no final. Portanto, (-1.5).toFixed(0) === "-2" e (1.5).toFixed(0) === "2" são declarações verdadeiras em todos os navegadores compatíveis com as especificações. Observe que esses valores são strings, não números. Observe também que tanto -1.5.toFixed(0) quanto -(1.5).toFixed(0) são === -2 (o número) devido à precedência do operador.

Inconsistências do navegador

A maioria dos navegadores modernos – ou, pelo menos, os que você deve ter suporte no momento da redação, exceto para o IE – devem implementar as especificações corretamente. (De acordo com o comentário de Renee , o problema toFixed que você apontou no Opera foi corrigido, presumivelmente desde que eles começaram a usar o mesmo mecanismo JS do Chrome.) Ainda vale a pena reiterar que, mesmo se as especificações fossem implementadas consistentemente em todos os navegadores, o comportamento Definido na especificação, particularmente para toFixed arredondamento, ainda pode ser um pouco intuitivo para desenvolvedores de JS “meros mortais” que esperam verdadeira precisão matemática – veja Javascript toFixed Not Rounding e este bug “funciona como pretendido” que foi arquivado no motor V8 JS por exemplo.

Conclusão

Em resumo, estas são duas funções diferentes com dois tipos de retorno diferentes e dois conjuntos diferentes de regras para arredondamento.

Como outros sugeriram, eu também gostaria de dizer “use qualquer function que se adapte ao seu caso de uso particular” (tomando um cuidado especial para observar as peculiaridades do toFixed , especialmente a implementação errônea do IE). Pessoalmente, gostaria de recomendar uma combinação explícita de Math.round/ceil/floor , novamente, como outros já mencionaram. Editar: … porém, depois de voltar e ler o seu esclarecimento, o seu caso de uso (arredondando para um número inteiro) definitivamente requer a function Math.round apropriadamente chamada.

toFixed () retorna um valor de string. De Javascript: O Guia Definitivo

Converte um número em uma string que contém um número especificado de dígitos após a casa decimal.

Math.round () retorna um inteiro.

Claramente, toFixed () parece ser mais usado para dinheiro, por exemplo,

‘$’ + 12.34253.toFixed (2) = ‘$ 12.34’

Parece uma grande pena que toFixed () não parece rodar corretamente!

Em vez de toFixed(0) use Math.ceil() ou Math.floor() , dependendo do que for necessário.

Definitivamente parece assim, se você está recebendo respostas inconsistentes.

Eu só posso adivinhar que sua intenção com usinFixed (0) é transformar um número decimal em um inteiro, ponto em que eu recomendo Math.floor (). Há um pouco mais de discussão sobre a melhor maneira de fazer isso nessa questão .