Например, я хочу показать список кнопок с 0,0,5, ... 5, которые переходят на каждые 0,5. Для этого я использую цикл for, и у кнопки 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';
}
}
В этом случае не должно быть ошибок округления, поскольку каждое значение является точным в IEEE 754. Но я изо всех сил, если я должен изменить его, чтобы избежать сравнения равенства с плавающей запятой:
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';
}
}
С одной стороны, оригинальный код более прост и понятен мне. Но есть одна вещь, которую я рассматриваю: я == STANDARD_LINE вводит в заблуждение младших товарищей по команде? Это скрывает тот факт, что числа с плавающей запятой могут иметь ошибки округления? Прочитав комментарии к этому посту:
кажется, что многие разработчики не знают, что некоторые числа с плавающей точкой точны. Следует ли мне избегать сравнений равенства чисел с плавающей запятой, даже если это допустимо в моем случае? Или я слишком обдумываю это?
i
во втором списке будут только целые числа. Попробуйте удалить второй/2.0
.button
нигде в вашей петле ничего не меняется. Как получить доступ к списку кнопок? Через индекс в массив или какой-то другой механизм? Если это по индексу доступа к массиву, это еще один аргумент в пользу переключения на целые числа.Ответы:
Я бы всегда избегал последовательных операций с плавающей точкой, если модель, которую я вычисляю, не требует их. Арифметика с плавающей запятой неинтуитивна для большинства и является основным источником ошибок. И говорить о случаях, когда это вызывает ошибки из тех, где это не так, - еще более тонкое различие!
Следовательно, использование чисел с плавающей запятой в качестве счетчиков циклов является дефектом, ожидающим возникновения, и, по крайней мере, потребует жирного комментария, объясняющего, почему здесь допустимо использовать 0,5, и что это зависит от конкретного числового значения. На этом этапе переписывание кода, чтобы избежать счетчиков с плавающей запятой, вероятно, будет более читабельным вариантом. И читабельность рядом с правильностью в иерархии профессиональных требований.
источник
DIFF must be an exactly-representable double that evenly divides STANDARD_LINE
. Если вы не хотите писать этот комментарий (и полагаетесь на то, что все будущие разработчики достаточно хорошо разбираются в двоичной64 IEEE754, чтобы понять его), не пишите код таким образом. т.е. не пишите код таким образом. Тем более, что это, вероятно, даже не более эффективно: сложение FP имеет большую задержку, чем сложение целых чисел, и это зависимость, переносимая циклами. Кроме того, компиляторы (даже JIT-компиляторы?), Вероятно, лучше делают циклы с целочисленными счетчиками.Как правило, циклы должны быть написаны так, чтобы думать о том, чтобы что-то делать n раз. Если вы используете индексы с плавающей запятой, это больше не вопрос о том, чтобы делать что-то n раз, а скорее о выполнении до тех пор, пока не будет выполнено условие. Если это условие оказывается очень похожим на то,
i<n
что ожидают многие программисты, тогда код, по-видимому, выполняет одно, а фактически другое, что может быть легко неверно истолковано программистами, скиммирующими код.Это несколько субъективно, но, по моему скромному мнению, если вы можете переписать цикл, чтобы использовать целочисленный индекс для цикла фиксированное число раз, вы должны сделать это. Поэтому рассмотрим следующую альтернативу:
Цикл работает в терминах целых чисел. В этом случае
i
является целым числом и такжеSTANDARD_LINE
приводится к целому числу. Это, конечно, изменило бы положение вашей стандартной линии, если бы произошло округление и аналогичным образомMAX
, так что вы все равно должны стремиться предотвратить округление для точного рендеринга. Однако у вас все еще есть преимущество в изменении параметров в виде пикселей, а не целых чисел, не беспокоясь о сравнении с плавающей запятой.источник
i
иSTANDARD_LINE
только выглядят как целые числа. Там нет никакого принуждения вообще, иDIFF
,MAX
иSTANDARD_LINE
все простоNumber
с.Number
s, используемые в качестве целых чисел, должны быть безопасны ниже2**53
, хотя они все еще являются числами с плавающей точкой.Я согласен со всеми другими ответами, что использование нецелочисленной переменной цикла, как правило, плохой стиль, даже в тех случаях, когда этот будет работать правильно. Но мне кажется, что есть еще одна причина, почему это плохой стиль здесь.
Ваш код «знает», что доступная ширина линии в точности кратна 0,5 от 0 до 5,0. Должно ли это? Похоже, это решение пользовательского интерфейса, которое может легко измениться (например, может быть, вы хотите, чтобы промежутки между доступными значениями ширины увеличивались по мере увеличения ширины. 0,25, 0,5, 0,75, 1,0, 1,5, 2,0, 2,5, 3,0, 4,0, 5,0 или что-то).
Ваш код «знает», что все доступные значения ширины строки имеют «хорошие» представления как в виде чисел с плавающей точкой, так и в виде десятичных дробей. Это также кажется чем-то, что может измениться. (Вы можете захотеть 0,1, 0,2, 0,3, ... в какой-то момент.)
Ваш код «знает», что текст, который нужно надеть на кнопки, - это просто то, во что превращает эти значения с плавающей точкой Javascript. Это также кажется чем-то, что может измениться. (Например, возможно, однажды вы захотите ширину, например, 1/3, которую вы, вероятно, не захотите отображать как 0.33333333333333 или что-то в этом роде. Или, возможно, вы захотите увидеть «1.0» вместо «1» для согласованности с «1.5» .)
Все это кажется мне проявлением единой слабости, которая является своего рода смешением слоев. Эти числа с плавающей точкой являются частью внутренней логики программного обеспечения. Текст, отображаемый на кнопках, является частью пользовательского интерфейса. Они должны быть более отдельными, чем в коде здесь. Понятия типа "какой из них по умолчанию должен быть выделен?" вопросы пользовательского интерфейса, и они, вероятно, не должны быть привязаны к этим значениям с плавающей точкой. И ваш цикл здесь действительно (или, по крайней мере, должен быть) циклом над кнопками , а не по ширине линии . Написанный таким образом, искушение использовать переменную цикла, принимающую нецелые значения, исчезает: вы просто используете последовательные целые числа или цикл for ... in / for ....
Мне кажется, что большинство случаев, когда можно испытать соблазн зацикливаться на нецелых числах, таковы: существуют другие причины, совершенно не связанные с числовыми проблемами, почему код должен быть организован по-другому. (Не во всех случаях; я могу представить, что некоторые математические алгоритмы могут быть наиболее аккуратно выражены в терминах цикла над нецелыми значениями.)
источник
Один запах кода использует плавающие циклы, как это.
Зацикливание может быть выполнено многими способами, но в 99,9% случаев вы должны придерживаться шага, равного 1, иначе наверняка возникнет путаница не только у младших разработчиков.
источник
Да, вы хотите избежать этого.
Числа с плавающей запятой - одна из самых больших ловушек для ничего не подозревающих программистов (что, по моему опыту, означает почти всех). От зависимости от тестов на равенство с плавающей запятой до представления денег как плавающей запятой - все это большая проблема. Добавление одного поплавка к другому является одним из самых больших правонарушителей. Есть целые тома научной литературы о таких вещах.
Используйте числа с плавающей запятой именно там, где они уместны, например, при выполнении реальных математических вычислений там, где они вам нужны (например, тригонометрия, построение графиков функций и т. Д.), И будьте очень осторожны при выполнении последовательных операций. Равенство прямо Знание того, какой именно набор чисел является точным по стандартам IEEE, очень загадочно, и я бы никогда не зависел от этого.
В вашем случае, будет , по Murphys закона, наступит момент , когда руководство хочет , чтобы вы не имеете 0,0, 0,5, 1,0 ... 0,0 , но, 0,4, 0,8 ... или что - то; вы вы будете немедленно BORKED, и ваш младший программист (или себя) будет отлаживать долго и упорно , пока не найдете проблему.
В вашем конкретном коде у меня действительно была бы целочисленная переменная цикла. Она представляет собой
i
кнопку th, а не текущий номер.И я бы, вероятно, ради большей ясности, не писал,
i/2
ноi*0.5
это совершенно ясно дает понять, что происходит.Примечание: как указано в комментариях, JavaScript не имеет отдельного типа для целых чисел. Но целые числа до 15 цифр гарантированно будут точными / безопасными (см. Https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer ), следовательно, для таких аргументов (" более запутанный / подверженный ошибкам при работе с целыми или нецелыми числами ") это подходящим образом близко к наличию отдельного типа" по духу "; при ежедневном использовании (циклы, экранные координаты, индексы массивов и т. д.) сюрпризов с целыми числами, представленными в
Number
виде JavaScript, не будет.источник
Я не думаю, что любое из ваших предложений хорошо. Вместо этого я бы ввел переменную для количества кнопок на основе максимального значения и расстояния. Тогда достаточно просто зациклить индексы самой кнопки.
Это может быть больше кода, но он также более читабелен и надежен.
источник
Вы можете избежать всего этого, вычисляя отображаемое значение, а не используя счетчик цикла в качестве значения:
источник
Арифметика с плавающей точкой медленная, а целочисленная арифметика быстрая, поэтому, когда я использую число с плавающей запятой, я не буду использовать его без необходимости, где могут использоваться целые числа. Полезно всегда думать о числах с плавающей запятой, даже о константах, как о приблизительных, с некоторой небольшой ошибкой. Во время отладки очень полезно заменять собственные числа с плавающей запятой на объекты с плавающей запятой плюс / минус, где каждое число рассматривается как диапазон, а не точка. Таким образом вы обнаружите прогрессивные неточности роста после каждой арифметической операции. Таким образом, «1,5» следует понимать как «некоторое число от 1,45 до 1,55», а «1,50» следует понимать как «некоторое число от 1,495 до 1,505».
источник