Чи слід уникати <= і> = під час використання цілих чисел, наприклад у циклі For? [зачинено]


15

Я пояснив своїм студентам, що тестування, що відповідає рівним тестуванню, не є надійним для плаваючих змінних, але добре для цілих чисел. У підручнику, який я використовую, сказано, що легше читати> і <, ніж> = і <=. Я певною мірою згоден, але в циклі For? Чи не чіткіше, щоб цикл вказав початкові та кінцеві значення?

Чи пропускаю я щось, на що автор підручника правильний?

Інший приклад - тести Range, такі як:

якщо бал> 89 бал = 'А',
інше, якщо бал> 79 бал = 'В' ...

Чому б просто не сказати: якщо оцінка> = 90?

loops 

11
На жаль, оскільки немає об'єктивної різниці в поведінці між цими варіантами, це складає опитування думки щодо того, що люди вважають інтуїтивнішим, а опитування не підходять для таких сайтів StackExchange, як цей.
Іксрек

1
Це не має значення. Дійсно.
Ауберон

4
Власне, це об'єктивно відповідає. Очікуйте ...
Роберт Харві

9
@Ixrec Мені завжди цікаво, що "найкраща практика" не вважається підходящою темою. Хто не хоче вдосконалювати чи зробити свій код більш читабельним? Якщо люди не згодні, ми всі дізнаємося більше сторін проблеми, і, можливо, ... навіть ... передумаємо! Фу, це було так важко сказати. Роберт Харві каже, що може відповісти на це об'єктивно. Це буде цікаво.

1
@nocomprende Переважно тому, що "найкраща практика" - це надзвичайно розпливчастий і надмірно використаний термін, який може посилатися на корисні поради, засновані на об'єктивних фактах про мову, але так само часто стосується "найпопулярнішої думки" (або думки того, хто використовує цей термін) коли насправді всі варіанти однаково дійсні та недійсні. У цьому випадку ви можете зробити об'єктивний аргумент лише обмеживши відповідь певними типами циклів, як це робив Роберт, і як ви вказали на себе у коментарі, який не відповідає повністю на запитання.
Іксрек

Відповіді:


36

У мовах програмування з фігурними дужками з масивами , що базуються на нулі , прийнято писати forпетлі так:

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

Це обходить всі елементи масиву, і це, безумовно, найпоширеніший випадок. Це дозволяє уникнути використання <=або >=.

Єдиний час, коли б це колись довелося змінити, це коли вам потрібно пропустити перший або останній елемент, або пройти його в протилежному напрямку, або пройти його від іншої початкової точки або до іншої кінцевої точки.

Для колекцій мовами, які підтримують ітератори, частіше це бачити:

foreach (var item in list) { }

Що цілком уникає порівнянь.

Якщо ви шукаєте жорстке і швидке правило щодо використання <=vs <, не існує жодного; використовуйте те, що найкраще виражає ваші наміри. Якщо ваш код повинен виражати поняття "Менше або рівне 55 миль на годину", то йому потрібно сказати <=, ні <.

Відповідати на ваше запитання щодо діапазонів оцінок >= 90має більше сенсу, оскільки 90 - фактичне граничне значення, а не 89.


9
Це не уникне цілих порівнянь, просто підмітає їх під килим, переміщуючи їх у реалізацію перелічувача. : P
Мейсон Уілер

2
Звичайно, але це вже не обхід масиву, чи не так?
Роберт Харві

4
Оскільки масиви є найпоширенішим випадком використання для forподібних циклів. Форма forциклу, яку я надав тут, буде миттєво впізнаваною для будь-якого розробника, який має хот досвід. Якщо ви хочете отримати більш конкретну відповідь на основі більш конкретного сценарію, вам потрібно включити її у своє запитання.
Роберт Харві

3
"використовувати те, що найкраще виражає ваші наміри" <- Це!
Джаспер Н. Брауер

3
@ JasperN.Brouwer Це як нульовий закон програмування. Усі правила стилю та умовні норми кодування руйнуються в найглибшому соромі, коли їхні мандати суперечать ясності коду.
Iwillnotexist Idonotexist

16

Це не має значення.

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

Тримайся! Це не рівнозначно!
OK, потім 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 це стосується обговорення >vs >=або дискусії про те, чи є дискусія >vs >=важливою? хоча, мабуть, найкраще уникати обговорення цього питання: p
Thanos Tintinidis

2
"Програми призначені для читання людьми і лише випадково для виконання комп'ютерів" - Дональд Кнут. Використовуйте стиль, за допомогою якого найпростіше читати, розуміти та підтримувати.
simpleuser

7

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

Однак більшість циклів буде рахувати від 0 (адже багато мов використовують 0 індексації для своїх масивів). Отже, канонічне для циклу в цих мовах є

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 (markup = 5; markup <= 10; markup ++) ... ? Чому я міняв би тест: розмітка <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);
}

Вибачте, будь ласка, "примітивну одержимість". Очевидно, ви б хотіли використовувати тут швидкість і гроші, відповідно, але я їх опустив для стислості. Сенс у тому: Використовуйте більш коротку версію, яка дозволяє зосередитись на бізнес-проблемі, яку ви хочете вирішити.


2
Приклад обмеження швидкості - поганий приклад. Їхати 90 км / год у зоні 90 км / год не вважається швидкістю. Обмеження швидкості включено.
ейдсонатор

ви абсолютно правильні, мозок пердеть з мого боку. не соромтесь відредагувати його, щоб використовувати кращий приклад, сподіваємось, точка не повністю втрачена.
сара

0

Як ви зазначали у своєму запитанні, тестування рівності на змінних float не є надійним.

Те саме стосується і <=та >=.

Однак не існує такої проблеми надійності для цілих типів. На мою думку, автор висловлював свою думку щодо того, що можна читати.

Погоджуєтесь ви з ним чи ні, звичайно, ваша думка.


Це загальноприйняте уявлення інших авторів та сивих в полі (насправді у мене сиве волосся)? Якщо це просто "Один думка репортера", я можу це сказати студентам. Власне, насправді. Ніколи раніше не чув про цю ідею.

Стать автора не має значення, але я все-таки оновив свою відповідь. Я вважаю за краще вибирати <або <=грунтуватися на тому, що найбільш природно для конкретної проблеми, яку я вирішую. Як зазначали інші, цикл FOR <має більше сенсу в мовах типу C. Є й інші випадки використання, які сприяють <=. Використовуйте всі інструменти, які є у вашому розпорядженні, коли та де це доречно.
Ден Пішельман

Не згоден. З цілими типами можуть виникнути проблеми з надійністю: у C врахуйте: for (unsigned int i = n; i >= 0; i--)або for (unsigned int i = x; i <= y; i++)якщо yце станеться UINT_MAX. На жаль, ці петлі назавжди.
jamesdlin

-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


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;

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


Так, я думаю, що це трохи далі в підручнику. Ще не розгляд. Оптимізація компілятора корисна для розуміння, і переповнення потрібно уникати, але це насправді не мотивація мого питання, що було більше з точки зору того, як люди розуміють код. Чи легше читати x> 5 або x> = 6? Ну, це залежить ...

Якщо ви не пишете для Arduino, максимальне значення для int буде трохи вище 65535.
Corey

1
@Corey: Максимальне значення uint16_t буде 65535 на будь-якій платформі, де існує тип; Я використовував uint16_t в першому прикладі, тому що я очікував би, що більше людей дізнаються точне максимальне значення uint16_t, ніж значення uint32_t. Те саме стосується будь-якого випадку. Другий приклад є агностичним щодо типу "int" і являє собою критичну поведінкову точку, яку багато людей не розуміють про сучасні компілятори.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.