<= And> = dovrebbe essere evitato quando si usano numeri interi, come in un ciclo For? [chiuso]


15

Ho spiegato ai miei studenti che la parità di test non è affidabile per le variabili float, ma va bene per i numeri interi. Il libro di testo che sto usando dice che è più facile da leggere> e <di> = e <=. Sono d'accordo in una certa misura, ma in un ciclo For? Non è più chiaro se il ciclo specifica i valori iniziale e finale?

Mi sto perdendo qualcosa di cui l'autore del libro di testo ha ragione?

Un altro esempio è nei test di intervallo come:

se il punteggio> 89 grado = 'A'
altrimenti se il punteggio> 79 grado = 'B' ...

Perché non dire semplicemente: se il punteggio> = 90?

loops 

11
Sfortunatamente, poiché non esiste alcuna differenza oggettiva nel comportamento tra queste opzioni, ciò equivale a un sondaggio di opinione su ciò che le persone considerano più intuitivo e i sondaggi non sono adatti per siti StackExchange come questo.
Ixrec,

1
Non importa Veramente.
Auberon,

4
In realtà, questo è oggettivamente responsabile. Stand by ...
Robert Harvey,

9
@Ixrec Trovo sempre interessante che la "migliore pratica" non sia considerata un argomento adatto. Chi non vuole migliorare o rendere più leggibile il proprio codice? Se le persone non sono d'accordo, impariamo tutti più lati del problema e potremmo ... persino ... cambiare idea! Uff, è stato così difficile da dire. Robert Harvey afferma di poter rispondere obiettivamente. Questo sarà interessante.

1
@nocomprende Principalmente perché "best practice" è un termine estremamente vago e abusato che può riferirsi a consigli utili basati su fatti oggettivi sulla lingua, ma altrettanto spesso si riferisce all'opinione più popolare (o l'opinione di chiunque stia usando il termine) quando in realtà tutte le opzioni sono ugualmente valide e non valide. In questo caso, puoi solo argomentare obiettivamente limitando la risposta a determinati tipi di loop come ha fatto Robert, e come hai sottolineato te stesso in un commento che non risponde completamente alla domanda.
Ixrec,

Risposte:


36

Nei linguaggi di programmazione a parentesi graffe con array a base zero , è consuetudine scrivere forloop come questo:

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

Questo attraversa tutti gli elementi dell'array ed è di gran lunga il caso più comune. Evita l'uso di <=o >=.

L'unica volta che questo dovrebbe mai cambiare è quando è necessario saltare il primo o l'ultimo elemento, oppure attraversarlo nella direzione opposta o attraversarlo da un punto iniziale diverso o verso un punto finale diverso.

Per le raccolte, nelle lingue che supportano gli iteratori, è più comune vedere questo:

foreach (var item in list) { }

Ciò evita del tutto i confronti.

Se stai cercando una regola dura e veloce su quando usare <=vs <, non ce n'è una; usa ciò che esprime al meglio il tuo intento. Se il tuo codice deve esprimere il concetto "Minore o uguale a 55 miglia all'ora", allora deve dire di <=no <.

Per rispondere alla tua domanda sulle gamme di voti, >= 90ha più senso, perché 90 è il valore limite effettivo, non 89.


9
Non evita del tutto i confronti, li sposta semplicemente sotto il tappeto spostandoli nell'implementazione dell'enumeratore. : P
Mason Wheeler,

2
Certo, ma questo non attraversa più un array, vero?
Robert Harvey,

4
Perché gli array sono il caso d'uso più comune per forloop come questo. La forma del forciclo che ho fornito qui sarà immediatamente riconoscibile a qualsiasi sviluppatore con un minimo di esperienza. Se desideri una risposta più specifica basata su uno scenario più specifico, devi includerla nella tua domanda.
Robert Harvey,

3
"usa ciò che esprime al meglio il tuo intento" <- Questo!
Jasper N. Brouwer,

3
@ JasperN.Brouwer È come una legge zeroth della programmazione. Tutte le regole di stile e le convenzioni di codifica crollano nella vergogna più profonda quando i loro mandati sono in conflitto con la chiarezza del codice.
Iwillnotexist Idonotexist del

16

Non importa

Ma per il bene della discussione, analizziamo le due opzioni: a > bvs a >= b.

Un attimo! Quelli non sono equivalenti!
OK, quindi a >= b -1vs a > bo a > bcontro a >= b +1.

Hm, a >bed a >= bentrambi sembrano migliori di a >= b - 1ea >= b +1 . Cosa sono 1comunque tutti questi ? Quindi direi che qualsiasi beneficio derivante dall'avere >invece >=o viceversa è eliminato dovendo aggiungere o sottrarre casuali 1.

E se fosse un numero? È meglio dire a > 7o a >= 6? Aspetta un secondo. Stiamo seriamente discutendo se è meglio usare >vs >=e ignorare le variabili hard coded? Quindi diventa davvero una questione se a > DAYS_OF_WEEKè meglio dia >= DAYS_OF_WEEK_MINUS_ONE ... o è a > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONEvs a >= NUMBER_OF_LEGS_IN_INSECT? E torniamo ad aggiungere / sottrarre 1s, solo questa volta in nomi di variabili. O magari discutendo se è meglio usare soglia, limite, massimo.

E sembra che non ci siano regole generali: dipende da cosa viene confrontato

Ma in realtà, ci sono cose molto più importanti da migliorare nel proprio codice e linee guida molto più obiettive e ragionevoli (ad es. Limite di caratteri X per riga) che hanno ancora delle eccezioni.


1
OK, non esiste una regola generale. (Mi chiedo perché il libro di testo sembra indicare una regola generale, ma posso lasciarlo andare.) Non vi è alcun consenso emergente sul fatto che fosse un memo che mi mancava.

2
@nocomprende perché gli standard di codifica e formattazione sono sia puramente soggettivi sia altamente controversi. Il più delle volte nessuno di loro conta, ma i programmatori continuano a condurre guerre sante su di loro. (sono importanti solo quando è probabile che il codice sciatto provochi errori)

1
Ho visto molte discussioni folli sugli standard di codifica, ma questo potrebbe prendere la torta. Davvero sciocco.
JimmyJames,

@JimmyJames si tratta della discussione di >vs >=o della discussione sul fatto che la discussione di >vs >=sia significativa? anche se probabilmente è meglio evitare di discuterne: p
Thanos Tintinidis,

2
"I programmi sono pensati per essere letti dagli umani e solo per inciso per l'esecuzione dei computer" - Donald Knuth. Usa lo stile che semplifica la lettura, la comprensione e la manutenzione.
simpleuser

7

Computazionalmente non vi è alcuna differenza nei costi quando si utilizza <o >rispetto a <=o>= . È calcolato altrettanto velocemente.

Tuttavia, la maggior parte dei loop conterà da 0 (poiché molte lingue usano l'indicizzazione 0 per i loro array). Quindi il canonico per loop in quelle lingue è

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

farlo con un <=richiederebbe di aggiungere un -1 da qualche parte per evitare l'off-by di un errore

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

o

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

Ovviamente se la lingua utilizza l'indicizzazione basata su 1, allora useresti <= come condizione limite.

La chiave è che i valori espressi nella condizione sono quelli della descrizione del problema. È più pulito da leggere

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

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

}

per un intervallo semiaperto di

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

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

}

e devo fare i conti con la matematica per sapere che non esiste un valore possibile tra 19 e 20


Vedo l'idea di "lunghezza", ma cosa succede se si utilizzano solo valori che provengono da qualche parte e sono destinati a scorrere da un valore iniziale a un valore finale. Un esempio di classe era la percentuale di markup da 5 a 10. Non è più chiaro dire: for (markup = 5; markup <= 10; markup ++) ... ? Perché dovrei passare a questo test: markup <11 ?

6
@nocomprende non lo faresti, se i valori limite della descrizione del problema sono 5 e 10, è meglio averli esplicitamente nel codice piuttosto che un valore leggermente modificato.
maniaco del cricchetto

@nocomprende il formato giusto è più evidente quando il limite superiore è un parametro: for(markup = 5; markup <= MAX_MARKUP; ++markup). Nient'altro lo complicherebbe eccessivamente.
Navin

1

Direi che il punto non è se dovresti usare> o> =. Il punto è usare qualunque cosa ti permetta di scrivere codice espressivo.

Se ritieni di dover aggiungere / sottrarre uno, considera l'utilizzo dell'altro operatore. Trovo che accadano cose buone quando inizi con un buon modello del tuo dominio. Quindi la logica si scrive da sola.

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

Questo è molto più espressivo di

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

In altri casi, è preferibile l'altro modo:

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

Molto meglio di

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

Per favore, scusa l'ossessione primitiva. Ovviamente vorresti usare un tipo Velocity e Money qui, rispettivamente, ma li ho omessi per brevità. Il punto è: utilizzare la versione più concisa e che consente di concentrarsi sul problema aziendale che si desidera risolvere.


2
L'esempio del limite di velocità è un cattivo esempio. Andare a 90 km / h in una zona a 90 km / h non è considerato eccesso di velocità. Un limite di velocità è incluso.
eidsonator,

hai assolutamente ragione, scoreggia cerebrale da parte mia. sentiti libero di modificarlo per usare un esempio migliore, speriamo che il punto non sia completamente perso.
Sara,

0

Come hai sottolineato nella tua domanda, il test per l'uguaglianza sulle variabili float non è affidabile.

Lo stesso vale per <=e >=.

Tuttavia, non esiste tale problema di affidabilità per i tipi interi. A mio avviso, l'autore ha espresso la sua opinione su quale sia più leggibile.

Indipendentemente dal fatto che tu sia d'accordo o meno con lui, ovviamente è la tua opinione.


È una visione generalmente condivisa da altri autori e capelli grigi sul campo (in realtà ho i capelli grigi)? Se si tratta solo di "One Reporter's Opinion", posso dirlo agli studenti. Ho, in realtà. Non ho mai sentito parlare di questa idea prima.

Il genere dell'autore è irrilevante, ma ho comunque aggiornato la mia risposta. Preferisco selezionare <o <=basarmi su ciò che è più naturale per il particolare problema che sto risolvendo. Come altri hanno sottolineato, in un ciclo FOR <ha più senso nei linguaggi di tipo C. Ci sono altri casi d'uso che favoriscono <=. Utilizzare tutti gli strumenti a vostra disposizione, quando e dove appropriato.
Dan Pichelman,

Disaccordo. Ci possono essere problemi di affidabilità con tipi interi: in C, considerare: for (unsigned int i = n; i >= 0; i--)o for (unsigned int i = x; i <= y; i++)se ysembra essere UINT_MAX. Oops, quelli si ripetono per sempre.
jamesdlin

-1

Ciascuno dei rapporti <, <=, >=, >e anche == e !=hanno i casi di utilizzo per il confronto di due valori in virgola mobile. Ognuno ha un significato specifico e dovrebbe essere scelto quello appropriato.

Fornirò esempi per casi in cui si desidera esattamente questo operatore per ciascuno di essi. (Attenzione ai NaN, però.)

  • Hai una costosa funzione pura fche accetta come input un valore in virgola mobile. Per velocizzare i tuoi calcoli, decidi di aggiungere una cache degli ultimi valori calcolati, ovvero una tabella di ricerca mappata xa f(x). Ti consigliamo di usarlo ==per confrontare gli argomenti.
  • Vuoi sapere se puoi dividere in modo significativo per un numero x? Probabilmente vuoi usare x != 0.0.
  • Vuoi sapere se un valore xè nell'intervallo unitario? (x >= 0.0) && (x < 1.0)è la condizione corretta.
  • Hai calcolato il determinante ddi una matrice e vuoi dire se è definito positivo? Non c'è motivo di usare nient'altro che d > 0.0.
  • Vuoi sapere se Σ n = 1, ..., ∞ n −α diverge? Proverei per alpha <= 1.0.

La matematica in virgola mobile (in generale) non è esatta. Ma ciò non significa che dovresti temerlo, trattarlo come magico e certamente non sempre trattare due quantità in virgola mobile uguali se sono dentro 1.0E-10. Farlo romperà davvero la tua matematica e farà accadere tutte le cose strane.

  • Ad esempio, se si utilizza il confronto fuzzy con la cache di cui sopra, si introdurranno errori esilaranti. Ma peggio ancora, la funzione non sarà più pura e l'errore dipenderà dai risultati precedentemente calcolati!
  • Sì, anche se x != 0.0ed yè un valore a virgola mobile finito, y / xnon è necessario che sia finito. Ma potrebbe essere rilevante sapere se y / xnon è finito a causa dell'overflow o perché l'operazione non era matematicamente ben definita per cominciare.
  • Se si dispone di una funzione che ha come prerequisito che un parametro di input xdeve trovarsi nell'intervallo di unità [0, 1), sarei davvero sconvolto se generasse un errore di asserzione quando chiamato con x == 0.0o x == 1.0 - 1.0E-14.
  • Il determinante che hai calcolato potrebbe non essere accurato. Ma se farai finta che la matrice non sia definita positiva quando lo è il determinante calcolato 1.0E-30, non si ottiene nulla. Tutto quello che hai fatto è stato aumentare la probabilità di dare la risposta sbagliata.
  • Sì, il tuo argomento alphapotrebbe essere influenzato da errori di arrotondamento e quindi alpha <= 1.0potrebbe essere vero anche se il vero valore matematico per l'espressione da cui è alphastato calcolato potrebbe essere stato davvero maggiore di 1. Ma non c'è nulla che tu possa fare al riguardo a questo punto.

Come sempre nell'ingegneria del software, gestire gli errori al livello appropriato e gestirli una sola volta. Se aggiungi errori di arrotondamento nell'ordine di 1.0E-10(questo sembra essere il valore magico che la maggior parte delle persone usa, non so perché) ogni volta che confronti quantità in virgola mobile, ti imbatterai presto in errori nell'ordine di 1.0E+10...


1
Una risposta eccellente, anche se è vero, solo per una domanda diversa da quella posta.
Dewi Morgan,

-2

Il tipo di condizionale utilizzato in un ciclo può limitare i tipi di ottimizzazioni che un compilatore può eseguire, nel bene e nel male. Ad esempio, dato:

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

un compilatore potrebbe presumere che la condizione di cui sopra dovrebbe causare l'uscita del loop dopo l'ennesimo loop di passaggio a meno che n non possa 65535 e il loop potrebbe uscire in qualche modo diverso da quelli che superano n. Se si applicano tali condizioni, il compilatore deve generare un codice che provocherebbe l'esecuzione del ciclo fino a quando qualcosa di diverso dalla condizione precedente lo fa uscire.

Se il ciclo fosse invece stato scritto come:

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

quindi un compilatore potrebbe tranquillamente supporre che il ciclo non debba mai essere eseguito più di n volte e potrebbe quindi essere in grado di generare codice più efficiente.

Si noti che qualsiasi overflow con tipi firmati può avere conseguenze spiacevoli. Dato:

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

Un compilatore potrebbe riscriverlo come:

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;

Un ciclo di questo tipo si comporterebbe in modo identico all'originale se non si verifica alcun overflow nei calcoli, ma potrebbe funzionare per sempre anche su piattaforme hardware in cui l'overflow di numeri interi avrebbe normalmente una semantica di wrapping coerente.


Sì, penso che sia un po 'più avanti nel libro di testo. Non ancora una considerazione. Le ottimizzazioni del compilatore sono utili da comprendere e bisogna evitare il trabocco, ma non è stata proprio la motivazione della mia domanda, più in termini di come le persone comprendono il codice. È più facile leggere x> 5 o x> = 6? Bene, dipende ...

A meno che tu non stia scrivendo per un Arduino, il valore massimo per int sarà un po 'più alto di 65535.
Corey

1
@Corey: il valore massimo per uint16_t sarà 65535 su qualsiasi piattaforma in cui esiste il tipo; Ho usato uint16_t nel primo esempio perché mi aspetterei che più persone conoscano il valore esatto massimo di un uint16_t rispetto a quello di un uint32_t. Lo stesso punto si applicherebbe in entrambi i casi. Il secondo esempio è agnostico per quanto riguarda il tipo di "int", e rappresenta un punto comportamentale critico che molte persone non riescono a riconoscere sui compilatori moderni.
supercat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.