Следует ли избегать <= и> = при использовании целых чисел, например, в цикле For? [закрыто]

15

Я объяснил своим студентам, что тестирование на равных не является надежным для переменных с плавающей точкой, но подходит для целых чисел. В учебнике, который я использую, сказано, что его легче читать> и <чем> = и <=. Я согласен в некоторой степени, но в цикле For? Разве не понятнее, чтобы цикл указывал начальные и конечные значения?

Я что-то упустил, о чем автор учебника прав?

Другой пример - тесты Range, такие как:

если балл> 89 балл = 'A',
иначе, если балл> 79 балл = 'B' ...

Почему бы просто не сказать: если оценка> = 90?


источник
11
К сожалению, поскольку между этими вариантами нет объективного различия в поведении, это сводится к опросу общественного мнения о том, что люди считают более интуитивным, и опросы не подходят для сайтов StackExchange, подобных этому.
Ixrec
1
Это не важно В самом деле.
Оберон
4
На самом деле, это объективно ответственно. Готовьтесь ...
Роберт Харви
9
@Ixrec Мне всегда было интересно, что «лучшая практика» не считается подходящей темой. Кто не хочет улучшать или делать их код более читабельным? Если люди не соглашаются, мы все узнаем больше сторон проблемы, и могли бы ... даже ... изменить наши умы! Тьфу, это было так сложно сказать. Роберт Харви говорит, что может ответить на него объективно. Это будет интересно.
1
@nocomprende В основном потому, что термин «лучшая практика» - это чрезвычайно расплывчатый и чрезмерно используемый термин, который может ссылаться на полезные советы, основанные на объективных фактах о языке, но так же часто относится к «наиболее популярному мнению» (или мнению того, кто использует этот термин). когда в действительности все варианты одинаково действительны и недействительны. В этом случае вы можете только сделать объективный аргумент, ограничив ответ определенными типами циклов, как это сделал Роберт, и как вы сами указали в комментарии, который не полностью отвечает на вопрос.
Ixrec

Ответы:

36

В языках программирования с фигурными скобками и массивами , начинающимися с нуля , принято писать forциклы следующим образом:

for (int i = 0; i < array.Length, i++) { }

Это пересекает все элементы в массиве и является наиболее распространенным случаем. Это позволяет избежать использования <=или >=.

Единственный раз, когда это нужно будет изменить, - это когда вам нужно пропустить первый или последний элемент, или пересечь его в противоположном направлении, или пересечь его из другой начальной точки или в другую конечную точку.

Для коллекций, в языках, которые поддерживают итераторы, более распространено видеть это:

foreach (var item in list) { }

Который полностью избегает сравнений.

Если вы ищете жесткое и быстрое правило относительно того, когда использовать <=против <, его нет; используйте то, что лучше всего выражает ваши намерения. Если ваш код должен выражать понятие «меньше или равно 55 милям в час», то он должен сказать <=, что нет <.

Чтобы ответить на ваш вопрос о диапазонах оценок, >= 90имеет больше смысла, потому что 90 является фактическим граничным значением, а не 89.

Роберт Харви
источник
9
Он не избегает сравнений полностью, он просто скрывает их, перемещая в реализацию перечислителя. : P
Мейсон Уилер
2
Конечно, но это уже не обход массива, не так ли?
Роберт Харви
4
Потому что массивы являются наиболее распространенным вариантом использования forтаких циклов. Форма forцикла, которую я предоставил здесь, будет мгновенно узнаваема любым разработчиком с небольшим опытом. Если вы хотите получить более конкретный ответ, основанный на более конкретном сценарии, вы должны включить его в свой вопрос.
Роберт Харви
3
«Используйте то, что лучше всего выражает ваши намерения» <- Это!
Джаспер Н. Брауэр
3
@ JasperN.Brouwer Это как нулевой закон программирования. Все правила стиля и соглашения о кодировании рушатся в глубочайшем позоре, когда их мандаты вступают в противоречие с ясностью кода.
Ивилнотексист Идонотексист
16

Это не важно

Но ради аргумента давайте проанализируем два варианта: a > bпротив a >= b.

Подожди! Это не эквивалентно!
Хорошо, тогда a >= b -1против a > bили a > bпротив a >= b +1.

Хм, a >bи a >= bоба выглядят лучше, чем a >= b - 1и a >= b +1. Что это вообще такое 1? Поэтому я бы поспорил, что любая выгода от наличия >вместо >=или наоборот устраняется необходимостью добавления или вычитания случайных 1s.

Но что, если это число? Это лучше сказать a > 7или a >= 6? Подожди секунду. Мы серьезно спорим, лучше ли использовать >vs >=и игнорировать жестко закодированные переменные? Таким образом, это действительно становится вопросом о том a > DAYS_OF_WEEK, лучше ли a >= DAYS_OF_WEEK_MINUS_ONE... или это a > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONEпротив a >= NUMBER_OF_LEGS_IN_INSECT? И мы вернулись к добавлению / вычитанию 1s, только на этот раз в именах переменных. Или, может быть, обсуждает, лучше ли использовать порог, лимит, максимум.

И похоже, что нет общего правила: это зависит от того, что сравнивается

Но на самом деле, есть гораздо более важные вещи, которые нужно улучшить в своем коде, и гораздо более объективные и разумные рекомендации (например, ограничение X-символов на строку), которые все еще имеют исключения.

Танос Тинтинидис
источник
1
ОК, нет общего правила. (Удивляясь, почему в учебнике, как представляется, изложено общее правило, я могу его оставить.) Нет единого мнения о том, что я пропустил эту записку.
2
@nocomprende, потому что стандарты кодирования и форматирования являются как чисто субъективными, так и весьма спорными. В большинстве случаев ни один из них не имеет значения, но программисты все еще ведут священные войны за них. (они имеют значение только тогда, когда небрежный код может привести к ошибкам)
1
Я видел много сумасшедших дискуссий о стандартах кодирования, но этот может просто сойти с ума. Действительно глупо.
JimmyJames
@JimmyJames - это обсуждение дискуссии >против >=или обсуждение того, имеет ли смысл обсуждение >против >=? хотя, вероятно, лучше избегать обсуждения этого: p
Thanos Tintinidis
2
«Программы предназначены для чтения людьми и только для того, чтобы компьютеры могли их выполнять», - Дональд Кнут. Используйте стиль, который облегчает чтение, понимание и поддержку.
simpleuser
7

В вычислительном отношении нет разницы в стоимости при использовании <или >по сравнению с <=или >=. Он вычисляется одинаково быстро.

Однако большинство циклов for будет считать от 0 (потому что многие языки используют индексирование 0 для своих массивов). Таким образом, канонический цикл for в этих языках

for(int i = 0; i < length; i++){
   array[i] = //...
   //...
}

выполнение этого с помощью a <=потребовало бы добавить -1 где-нибудь, чтобы избежать одной ошибки

for(int i = 1; i <= length; i++){
   array[i-1] = //...
   //...
}

или

for(int i = 0; i <= length-1; i++){
   array[i] = //...
   //...
}

Конечно, если в языке используется индексирование на основе 1, вы должны использовать <= в качестве ограничивающего условия.

Ключ заключается в том, что значения, выраженные в условии, являются значениями из описания проблемы. Читать чище

if(x >= 10 && x < 20){

} else if(x >= 20 && x < 30){

}

для полуоткрытого интервала, чем

if(x >= 10 && x <= 19){

} else if(x >= 20 && x <= 29){

}

и должны сделать математику, чтобы знать, что между 19 и 20 не может быть значения

чокнутый урод
источник
Я вижу идею "длины", но что, если вы просто используете значения, которые откуда-то пришли и предназначены для итерации от начального значения до конечного значения. Примером класса был процент разметки от 5 до 10. Разве не проще сказать: for (разметка = 5; разметка <= 10; разметка ++) ... ? Зачем мне менять на него тест: разметка <11 ?
6
@nocomprende, вы бы не стали, если граничные значения из вашего описания проблемы равны 5 и 10, тогда лучше иметь их явно в коде, а не слегка измененное значение.
фрик с трещоткой
@nocomprende Правильный формат является более очевидным , когда верхняя граница является параметром: for(markup = 5; markup <= MAX_MARKUP; ++markup). Все остальное было бы слишком сложным.
Навин
1

Я бы сказал, что дело не в том, должны ли вы использовать> или> =. Смысл в том, чтобы использовать все, что позволяет писать выразительный код.

Если вы обнаружите, что вам нужно добавить / вычесть один, рассмотрите возможность использования другого оператора. Я считаю, что хорошие вещи случаются, когда вы начинаете с хорошей моделью своего домена. Тогда логика пишет сама.

bool IsSpeeding(int kilometersPerHour)
{
    const int speedLimit = 90;
    return kilometersPerHour > speedLimit;
}

Это гораздо более выразительно, чем

bool IsSpeeding(int kilometersPerHour)
{
    const int speedLimit = 90;
    return kilometersPerHour >= (speedLimit + 1);
}

В других случаях предпочтителен другой способ:

bool CanAfford(decimal price, decimal balance)
{
    return balance >= price;
}

Намного лучше чем

bool CanAfford(decimal price, decimal balance)
{
    const decimal epsilon = 0e-10m;
    return balance > (price - epsilon);
}

Пожалуйста, извините за "примитивную одержимость". Очевидно, вы хотели бы использовать здесь Velocity- и Money-type соответственно, но я для краткости опустил их. Дело в том, что: используйте более краткую версию, которая позволит вам сосредоточиться на бизнес-проблеме, которую вы хотите решить.

Сара
источник
2
Пример ограничения скорости - плохой пример. Скорость 90 км / ч в зоне 90 км / ч не считается превышением скорости. Ограничение скорости включено.
Eidsonator
ты абсолютно прав, мозги пердят с моей стороны. не стесняйтесь редактировать это, чтобы использовать лучший пример, мы надеемся, что точка не полностью потеряна.
Сара
0

Как вы указали в своем вопросе, проверка на равенство по переменным с плавающей точкой не является надежной.

То же самое относится <=и к >=.

Однако для целочисленных типов такой проблемы с надежностью нет. По моему мнению, автор высказывала свое мнение о том, что является более читабельным.

Согласны ли вы с ним или нет, это, конечно, ваше мнение.

Дэн Пичельман
источник
Является ли это общепринятым мнением других авторов и седых волос в полевых условиях (на самом деле у меня седые волосы)? Если это просто «Мнение одного репортера», я могу сказать это студентам. У меня есть, на самом деле. Никогда не слышал об этой идее раньше.
Пол автора не имеет значения, но я все равно обновил свой ответ. Я предпочитаю выбирать <или <=основываться на том, что наиболее естественно для конкретной проблемы, которую я решаю. Как уже отмечали другие, цикл FOR <имеет больше смысла в языках C-типа. Есть другие варианты использования, которые одобряют <=. Используйте все инструменты в вашем распоряжении, когда и где это уместно.
Дан Пихельман
Не согласен. Там могут быть проблемы надежности с целочисленными типами: в C, рассмотреть следующие вопросы: for (unsigned int i = n; i >= 0; i--)или , for (unsigned int i = x; i <= y; i++)если yслучается UINT_MAX. Ой, эти петли навсегда.
Джеймсдлин
-1

Каждый из отношений <, <=, >=, >а также == и !=имеют примеры использования для сравнения двух значений с плавающей точкой. Каждый из них имеет конкретное значение, и следует выбрать соответствующий.

Я приведу примеры для случаев, когда вам нужен именно этот оператор для каждого из них. (Будьте в курсе NaNs, хотя.)

  • У вас есть дорогая чистая функция, fкоторая принимает значение с плавающей запятой в качестве входных данных. Для того , чтобы ускорить вычисления, вы решили добавить кэш самых последних вычисленных значений, то есть, отображение таблицы поиска xв f(x). Вы действительно хотите использовать ==для сравнения аргументов.
  • Вы хотите знать, можете ли вы поделить смысл на некоторое число x? Вы, вероятно, хотите использовать x != 0.0.
  • Вы хотите знать, находится ли значение xв единичном интервале? (x >= 0.0) && (x < 1.0)это правильное условие.
  • Вы вычислили определитель dматрицы и хотите сказать, положительно ли она определена? Нет причин использовать что-либо еще, кроме d > 0.0.
  • Вы хотите знать, расходится ли Σ n = 1,…, ∞ n −α ? Я бы проверил alpha <= 1.0.

Математика с плавающей точкой (в общем) не является точной. Но это не значит, что вы должны бояться этого, относиться к нему как к магии и, конечно, не всегда относиться к двум величинам с плавающей запятой, равным, если они находятся внутри 1.0E-10. Это действительно нарушит вашу математику и вызовет все странные вещи.

  • Например, если вы используете нечеткое сравнение с кешем, упомянутым выше, вы будете вводить смешные ошибки. Но что еще хуже, функция больше не будет чистой и ошибка будет зависеть от ранее вычисленных результатов!
  • Да, даже если x != 0.0и yявляется конечным значением с плавающей точкой, y / xне обязательно быть конечным. Но может быть уместно узнать, y / xне является ли оно конечным из-за переполнения или потому, что операция не была математически хорошо определена с самого начала.
  • Если у вас есть функция, которая имеет предварительное условие, что входной параметр xдолжен быть в единичном интервале [0, 1), я был бы очень расстроен, если бы он вызвал ошибку подтверждения при вызове с помощью x == 0.0или x == 1.0 - 1.0E-14.
  • Определитель, который вы вычислили, может быть неточным. Но если вы собираетесь делать вид, что матрица не является положительно определенной, когда вычисляемый детерминант 1.0E-30, ничего не получится. Все, что вы делали, это увеличивало вероятность дать неправильный ответ.
  • Да, на ваш аргумент alphaмогут повлиять ошибки округления, и, следовательно, он alpha <= 1.0может быть истинным, даже если истинное математическое значение для выражения alphaбыло действительно больше 1. Но с этим вы ничего не могли бы поделать.

Как всегда в разработке программного обеспечения, обрабатывайте ошибки на соответствующем уровне и обрабатывайте их только один раз. Если вы добавляете ошибки округления порядка 1.0E-10(кажется, это магическое значение, которое использует большинство людей, я не знаю почему) каждый раз, когда вы сравниваете величины с плавающей запятой, вы скоро будете иметь ошибки порядка 1.0E+10...

5gon12eder
источник
1
Самый превосходный ответ, хотя и общепринятый, только для вопроса, отличного от заданного.
Деви Морган
-2

Тип условия, используемый в цикле, может ограничивать виды оптимизаций, которые может выполнять компилятор, к лучшему или к худшему. Например, учитывая:

uint16_t n = ...;
for (uint16_t i=1; i<=n; i++)
  ...  [loop doesn't modify i]

компилятор может предположить, что вышеприведенное условие должно заставить цикл завершиться после цикла n-го прохода, если n не может 65535, и цикл может выйти каким-либо иным способом, кроме того, что i превысит n. Если эти условия применимы, компилятор должен сгенерировать код, который заставит цикл работать до тех пор, пока что-либо, кроме указанного выше условия, не заставит его завершиться.

Если цикл был записан как:

uint16_t n = ...;
for (uint16_t ctr=0; ctr<n; ctr++)
{
  uint16_t i = ctr+1;
  ... [loop doesn't modify ctr]
}

тогда компилятор может с уверенностью предположить, что цикл никогда не должен будет выполняться более n раз и, таким образом, сможет генерировать более эффективный код.

Обратите внимание, что любое переполнение со знаком типов может иметь неприятные последствия. Данный:

int total=0;
int start,lim,mult; // Initialize values somehow...
for (int i=start; i<=lim; i++)
  total+=i*mult;

Компилятор может переписать это как:

int total=0;
int start,lim,mult; // Initialize values somehow...
int loop_top = lim*mult;
for (int i=start; i<=loop_top; i+=mult)
  total+=i;

Такой цикл будет вести себя идентично исходному, если в вычислениях не будет переполнения, но он может работать вечно даже на аппаратных платформах, где целочисленное переполнение обычно имеет согласованную семантику обтекания.

Supercat
источник
Да, я думаю, что это немного дальше в учебнике. Пока не рассматривается. Оптимизацию компилятора полезно понять, и следует избегать переполнения, но на самом деле это не было мотивацией для моего вопроса, который был скорее с точки зрения понимания кода людьми. Легче ли читать x> 5 или x> = 6? Ну, это зависит ...
Если вы не пишете для Arduino, максимальное значение для int будет немного выше, чем 65535.
Кори
1
@Corey: максимальное значение для uint16_t будет 65535 на любой платформе, где существует тип; Я использовал uint16_t в первом примере, потому что я ожидал, что больше людей будут знать точное максимальное значение uint16_t, чем uint32_t. То же самое применимо в любом случае. Второй пример является независимым в отношении типа «int» и представляет собой критическую точку поведения, которую многие люди не могут понять о современных компиляторах.
суперкат