Por exemplo, quero mostrar uma lista de botões de 0,0,5, ... 5, que salta para cada 0,5. Eu uso um loop for para fazer isso e tenho cores diferentes no botão STANDARD_LINE:
var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;
for(var i=0;i<=MAX;i=i+DIFF){
button.text=i+'';
if(i==STANDARD_LINE){
button.color='red';
}
}
Nesse caso, não deve haver erros de arredondamento, pois cada valor é exato no IEEE 754. Mas estou lutando para alterá-lo para evitar a comparação de igualdade de ponto flutuante:
var MAX=10;
var STANDARD_LINE=3;
for(var i=0;i<=MAX;i++){
button.text=i/2.0+'';
if(i==STANDARD_LINE/2.0){
button.color='red';
}
}
Por um lado, o código original é mais simples e encaminhado para mim. Mas há uma coisa que estou considerando: eu == STANDARD_LINE engana colegas juniores? Esconde o fato de que números de ponto flutuante podem ter erros de arredondamento? Depois de ler os comentários deste post:
parece que muitos desenvolvedores não sabem que alguns números flutuantes são exatos. Devo evitar comparações de igualdade de número flutuante, mesmo que seja válido no meu caso? Ou estou pensando demais sobre isso?
i
sempre serão números inteiros na segunda listagem. Tente remover o segundo/2.0
.button
não muda em nenhum lugar do seu loop. Como é acessada a lista de botões? Via índice em array ou algum outro mecanismo? Se for pelo acesso do índice a uma matriz, esse é outro argumento a favor da mudança para números inteiros.Respostas:
Eu sempre evitaria operações sucessivas de ponto flutuante, a menos que o modelo que estou computando as exija. A aritmética de ponto flutuante é pouco intuitiva para a maioria e a principal fonte de erros. E dizer os casos em que causa erros daqueles em que não ocorre é uma distinção ainda mais sutil!
Portanto, o uso de flutuadores como contadores de loop é um defeito que está esperando para acontecer e exigiria, no mínimo, um comentário geral explicando por que não há problema em usar 0,5 aqui e que isso depende do valor numérico específico. Nesse ponto, reescrever o código para evitar contadores flutuantes provavelmente será a opção mais legível. E a legibilidade está próxima da correção na hierarquia dos requisitos profissionais.
fonte
DIFF must be an exactly-representable double that evenly divides STANDARD_LINE
. Se você não quiser escrever esse comentário (e confiar em todos os futuros desenvolvedores para saber o suficiente sobre o ponto flutuante IEEE754 binary64 para entendê-lo), não escreva o código dessa maneira. ou seja, não escreva o código dessa maneira. Especialmente porque provavelmente não é ainda mais eficiente: a adição de FP possui uma latência mais alta que a adição de números inteiros e é uma dependência de loop. Além disso, os compiladores (mesmo os compiladores JIT?) Provavelmente se saem melhor ao criar loops com contadores inteiros.Como regra geral, os loops devem ser escritos de maneira a pensar em fazer algo n vezes. Se você estiver usando índices de ponto flutuante, não se trata mais de fazer algo n vezes, mas de executar até que uma condição seja atendida. Se essa condição for muito semelhante à
i<n
esperada por muitos programadores, o código parece estar fazendo uma coisa quando na verdade está fazendo outra, o que pode ser facilmente mal interpretado pelos programadores que estão analisando o código.É um pouco subjetivo, mas, na minha humilde opinião, se você pode reescrever um loop para usar um índice inteiro para repetir um número fixo de vezes, deve fazê-lo. Portanto, considere a seguinte alternativa:
O loop funciona em termos de números inteiros. Nesse caso,
i
é um número inteiro e tambémSTANDARD_LINE
é coagido a um número inteiro. Evidentemente, isso mudaria a posição de sua linha padrão se houvesse arredondamento e o mesmo aconteceMAX
, portanto, você deve se esforçar para evitar o arredondamento para obter uma renderização precisa. No entanto, você ainda tem a vantagem de alterar os parâmetros em termos de pixels e não números inteiros sem precisar se preocupar com a comparação de pontos flutuantes.fonte
i
eSTANDARD_LINE
apenas se parecem com números inteiros. Não há coerção, eDIFF
,MAX
eSTANDARD_LINE
são todos apenasNumber
s.Number
s usados como números inteiros devem ser seguros abaixo2**53
, ainda são números de ponto flutuante.Concordo com todas as outras respostas de que o uso de uma variável de loop não inteiro geralmente é um estilo ruim, mesmo em casos como este em que funcionará corretamente. Mas parece-me que há outra razão pela qual é um estilo ruim aqui.
Seu código "sabe" que as larguras de linha disponíveis são precisamente os múltiplos de 0,5, de 0 a 5,0. Deveria? Parece que é uma decisão da interface do usuário que pode mudar facilmente (por exemplo, talvez você queira que as lacunas entre as larguras disponíveis sejam maiores do que as larguras. 0,25, 0,5, 0,75, 1,0, 1,5, 2,0, 2,5, 3,0, 4,0, 5.0 ou algo assim).
Seu código "sabe" que todas as larguras de linhas disponíveis têm representações "agradáveis", tanto como números de ponto flutuante quanto como decimais. Isso também parece algo que pode mudar. (Você pode querer 0,1, 0,2, 0,3, ... em algum momento.)
Seu código "sabe" que o texto para colocar nos botões é simplesmente o que o Javascript transforma esses valores de ponto flutuante. Isso também parece algo que pode mudar. (Por exemplo, talvez um dia você queira larguras como 1/3, que provavelmente não deseja exibir como 0,33333333333333 ou qualquer outra coisa. Ou talvez queira ver "1,0" em vez de "1" para consistência com "1,5" .)
Tudo isso me parece manifestações de uma única fraqueza, que é uma espécie de mistura de camadas. Esses números de ponto flutuante fazem parte da lógica interna do software. O texto mostrado nos botões faz parte da interface do usuário. Eles devem estar mais separados do que no código aqui. Noções como "qual desses é o padrão que deve ser destacado?" são assuntos da interface do usuário e provavelmente não devem estar vinculados a esses valores de ponto flutuante. E seu loop aqui é realmente (ou pelo menos deveria ser) um loop sobre botões , não sobre larguras de linha . Assim, a tentação de usar uma variável de loop que aceita valores não inteiros desaparece: você usaria números inteiros sucessivos ou um loop for ... in / for ... do loop.
Meu sentimento é que a maioria dos casos em que se pode tentar repetir números não inteiros é assim: existem outras razões, totalmente não relacionadas a questões numéricas, pelas quais o código deve ser organizado de maneira diferente. (Nem todos os casos; posso imaginar que alguns algoritmos matemáticos podem ser expressos com mais precisão em termos de um loop sobre valores não inteiros.)
fonte
Um cheiro de código está usando flutuadores em loop assim.
O looping pode ser feito de várias maneiras, mas em 99,9% dos casos, você deve manter um incremento de 1 ou definitivamente haverá confusão, não apenas pelos desenvolvedores juniores.
fonte
Sim, você deseja evitar isso.
Os números de ponto flutuante são uma das maiores armadilhas para o programador desavisado (o que significa, na minha experiência, quase todo mundo). Dependendo dos testes de igualdade de ponto flutuante, até representar o dinheiro como ponto flutuante, é tudo uma grande confusão. A soma de um flutuador sobre o outro é um dos maiores infratores. Existem volumes inteiros de literatura científica sobre coisas assim.
Use números de ponto flutuante exatamente nos locais onde eles são apropriados, por exemplo, ao fazer cálculos matemáticos reais onde você precisar deles (como trigonometria, gráficos de funções de plotagem etc.) e seja super cuidadoso ao realizar operações seriais. A igualdade está certa. O conhecimento sobre qual conjunto específico de números é exato pelos padrões IEEE é muito misterioso e eu nunca dependeria disso.
No seu caso, não vai , pela Lei Murphys, vem o ponto em que a gestão quer que você não tem 0.0, 0.5, 1.0 ... mas 0,0, 0,4, 0,8 ... ou o que quer; você será enviado imediatamente, e seu programador júnior (ou você mesmo) irá depurar por muito tempo até encontrar o problema.
No seu código específico, eu realmente teria uma variável de loop inteiro. Representa o
i
botão th, não o número em execução.E eu provavelmente, por uma questão de clareza extra, não escreveria,
i/2
mas oi*0.5
que deixaria bem claro o que está acontecendo.Nota: conforme indicado nos comentários, o JavaScript não possui realmente um tipo separado para números inteiros. Mas números inteiros de até 15 dígitos são garantidos como precisos / seguros (consulte https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer ), portanto, para argumentos como este ("é isso mais confuso / propenso a erros de trabalhar com números inteiros ou não com números inteiros "), isso é apropriado para ter um tipo separado" em espírito "; no uso diário (loops, coordenadas de tela, índices de matriz etc.), não haverá surpresas com números inteiros representados
Number
como JavaScript.fonte
Não acho que nenhuma das suas sugestões seja boa. Em vez disso, eu introduziria uma variável para o número de botões com base no valor máximo e no espaçamento. Então, é simples o suficiente para percorrer os índices do botão em si.
Pode ser mais código, mas também é mais legível e mais robusto.
fonte
Você pode evitar tudo calculando o valor que está mostrando, em vez de usar o contador de loop como o valor:
fonte
A aritmética de ponto flutuante é lenta e a aritmética de número inteiro é rápida; portanto, quando eu uso o ponto flutuante, não o utilizaria desnecessariamente onde números inteiros podem ser usados. É útil pensar sempre em números de ponto flutuante, mesmo constantes, como aproximados, com algum pequeno erro. É muito útil durante a depuração substituir números de ponto flutuante nativos por objetos de ponto flutuante mais / menos, onde você trata cada número como um intervalo em vez de um ponto. Dessa forma, você descobre imprecisões crescentes progressivas após cada operação aritmética. Portanto, "1.5" deve ser considerado "algum número entre 1,45 e 1,55" e "1,50" deve ser considerado "algum número entre 1,495 e 1,505".
fonte