¿Deben evitarse <= y> = cuando se usan enteros, como en un bucle For? [cerrado]

15

Les he explicado a mis alumnos que las pruebas equivalentes no son confiables para las variables flotantes, pero están bien para los enteros. El libro de texto que estoy usando dice que es más fácil de leer> y <que> = y <=. Estoy de acuerdo en cierta medida, pero en un bucle For? ¿No es más claro que el ciclo especifique los valores inicial y final?

¿Me estoy perdiendo algo sobre lo que el autor del libro de texto tiene razón?

Otro ejemplo es en pruebas de alcance como:

if score> 89 grade = 'A'
else if score> 79 grade = 'B' ...

¿Por qué no decir simplemente: si puntaje> = 90?


fuente
11
Desafortunadamente, dado que no existe una diferencia objetiva en el comportamiento entre estas opciones, esto equivale a una encuesta de opinión sobre lo que la gente considera más intuitiva, y las encuestas no son adecuadas para sitios de StackExchange como este.
Ixrec
1
No importa. De Verdad.
Auberon
44
En realidad, esto es objetivamente responsable. Stand by ...
Robert Harvey
99
@Ixrec Siempre me parece interesante que la "mejor práctica" no se considere un tema adecuado. ¿Quién no quiere mejorar o hacer que su código sea más legible? Si las personas no están de acuerdo, todos aprendemos más aspectos del problema y podríamos ... incluso ... ¡cambiar de opinión! Ugh, eso fue muy difícil de decir. Robert Harvey dice que puede responderlo objetivamente. Esto sera interesante.
1
@nocomprende Principalmente porque "mejor práctica" es un término extremadamente vago y usado en exceso que puede referir consejos útiles basados ​​en hechos objetivos sobre el idioma, pero con la misma frecuencia se refiere a la "opinión más popular" (o la opinión de quien está usando el término) cuando en realidad todas las opciones son igualmente válidas e inválidas. En este caso, solo puede hacer un argumento objetivo al restringir la respuesta a ciertos tipos de bucles como lo hizo Robert, y como lo señaló en un comentario que no responde completamente la pregunta.
Ixrec

Respuestas:

36

En los lenguajes de programación con llaves y matrices basadas en cero , es costumbre escribir forbucles como este:

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

Esto atraviesa todos los elementos de la matriz y es, con mucho, el caso más común. Evita el uso de <=o >=.

El único momento en que esto debería cambiar es cuando necesita omitir el primer o el último elemento, o atravesarlo en la dirección opuesta, o atravesarlo desde un punto de inicio diferente o un punto final diferente.

Para las colecciones, en idiomas que admiten iteradores, es más común ver esto:

foreach (var item in list) { }

Lo que evita las comparaciones por completo.

Si está buscando una regla dura y rápida sobre cuándo usar <=vs <, no hay una; usa lo que mejor exprese tu intención. Si su código necesita expresar el concepto "Menor o igual a 55 millas por hora", entonces necesita decir que <=no <.

Para responder a su pregunta sobre los rangos de calificaciones, >= 90tiene más sentido, porque 90 es el valor límite real, no 89.

Robert Harvey
fuente
99
No evita las comparaciones por completo, solo las barre debajo de la alfombra moviéndolas a la implementación del enumerador. : P
Mason Wheeler
2
Por supuesto, pero eso ya no atraviesa una matriz, ¿verdad?
Robert Harvey
44
Debido a que las matrices son el caso de uso más común para forbucles como este. La forma de forbucle que proporcioné aquí será reconocible instantáneamente para cualquier desarrollador con una mínima experiencia. Si desea una respuesta más específica basada en un escenario más específico, debe incluirla en su pregunta.
Robert Harvey
33
"usa lo que mejor exprese tu intención" <- ¡Esto!
Jasper N. Brouwer
33
@ JasperN.Brouwer Es como una ley cero de programación. Todas las reglas de estilo y las convenciones de codificación colapsan con profunda vergüenza cuando sus mandatos entran en conflicto con la claridad del código.
Iwillnotexist Idonotexist
16

No importa.

Pero por el bien del argumento, vamos a analizar las dos opciones: a > bvs a >= b.

¡Aférrate! ¡Esos no son equivalentes!
OK, entonces a >= b -1vs a > bo a > bvs a >= b +1.

Hm, a >by a >= bambos se ven mejor que a >= b - 1y a >= b +1. ¿Qué son todos estos 1s de todos modos? Por lo tanto, argumentaría que cualquier beneficio de tener en >lugar de >=o viceversa se elimina al tener que sumar o restar 1s aleatorios .

Pero, ¿y si es un número? ¿Es mejor decir a > 7o a >= 6? Espera un segundo. ¿Estamos discutiendo seriamente si es mejor usar >vs >=e ignorar las variables codificadas? Entonces, realmente se convierte en una cuestión de si a > DAYS_OF_WEEKes mejor que a >= DAYS_OF_WEEK_MINUS_ONE... ¿o es a > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONEvs a >= NUMBER_OF_LEGS_IN_INSECT? Y volvemos a sumar / restar 1s, solo que esta vez en nombres de variables. O tal vez debatir si es mejor usar umbral, límite, máximo.

Y parece que no hay una regla general: depende de lo que se compara

Pero realmente, hay cosas mucho más importantes para mejorar en el código de uno y pautas mucho más objetivas y razonables (por ejemplo, límite de caracteres X por línea) que todavía tienen excepciones.

Thanos Tintinidis
fuente
1
OK, no hay una regla general. (Me pregunto por qué el libro de texto parece indicar una regla general, pero puedo dejarlo ir). No existe un consenso emergente de que sea un memo que me haya perdido.
2
@nocomprende porque los estándares de codificación y formato son puramente subjetivos y altamente polémicos. La mayoría de las veces ninguno de ellos importa, pero los programadores aún libran guerras santas sobre ellos. (solo importan cuando el código descuidado puede
1
He visto muchas discusiones locas sobre los estándares de codificación, pero esta podría tomar el pastel. Realmente tonto.
JimmyJames
@JimmyJames, ¿se trata de la discusión de >vs >=o de si la discusión de >vs >=es significativa? aunque probablemente sea mejor evitar discutir esto: p
Thanos Tintinidis
2
"Los programas están destinados a ser leídos por humanos y solo de manera incidental para que las computadoras los ejecuten" - Donald Knuth. Use el estilo que lo hace más fácil de leer, comprender y mantener.
simpleuser
7

Computacionalmente no hay diferencia en el costo cuando se usa <o se >compara con <=o >=. Se calcula igual de rápido.

Sin embargo, la mayoría de los bucles contarán desde 0 (porque muchos idiomas usan indexación 0 para sus matrices). Entonces, el bucle for canónico en esos idiomas es

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

hacer esto con un <=requeriría que agregue un -1 en algún lugar para evitar el error de off-by

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

o

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

Por supuesto, si el lenguaje usa indexación basada en 1, entonces usaría <= como condición limitante.

La clave es que los valores expresados ​​en la condición son los de la descripción del problema. Es más limpio leer

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

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

}

por un intervalo medio abierto que

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

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

}

y tengo que hacer los cálculos para saber que no hay un valor posible entre 19 y 20

monstruo de trinquete
fuente
Veo la idea de "longitud", pero ¿qué pasa si solo está utilizando valores que provienen de algún lugar y están destinados a iterar de un valor inicial a un valor final? Un ejemplo de clase fue el porcentaje de marcado de 5 a 10. ¿No es más claro decir: for (markup = 5; markup <= 10; markup ++) ... ? ¿Por qué debería cambiar a prueba: marcado <11 ?
66
@nocomprende que no lo haría, si los valores límite de la descripción de su problema son 5 y 10, entonces es mejor tenerlos explícitamente en el código en lugar de un valor ligeramente cambiado.
monstruo de trinquete
@nocomprende El formato correcto es más evidente cuando el límite superior es un parámetro: for(markup = 5; markup <= MAX_MARKUP; ++markup). Cualquier otra cosa sería complicarlo demasiado.
Navin
1

Yo diría que el punto no es si debes usar> o> =. El punto es usar lo que te permita escribir código expresivo.

Si encuentra que necesita sumar / restar uno, considere usar el otro operador. Creo que suceden cosas buenas cuando comienzas con un buen modelo de tu dominio. Entonces la lógica se escribe sola.

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

Esto es mucho más expresivo que

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

En otros casos, la otra forma es preferible:

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

Mucho mejor que

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

Por favor, disculpe la "obsesión primitiva". Obviamente, querrías usar un tipo de velocidad y dinero aquí respectivamente, pero los omití por brevedad. El punto es: use la versión que sea más concisa y que le permita enfocarse en el problema comercial que desea resolver.

sara
fuente
2
El ejemplo de límite de velocidad es un mal ejemplo. Ir a 90 km / h en una zona de 90 km / h no se considera exceso de velocidad. Un límite de velocidad es inclusivo.
Eidsonator
tienes toda la razón, pedo cerebral de mi parte. siéntase libre de editarlo para usar un mejor ejemplo, espero que el punto no se pierda por completo.
Sara
0

Como señaló en su pregunta, las pruebas de igualdad en variables flotantes no son confiables.

Lo mismo es válido para <=y >=.

Sin embargo, no existe tal problema de confiabilidad para los tipos enteros. En mi opinión, la autora estaba expresando su opinión sobre cuál es más legible.

Si usted está o no de acuerdo con él es, por supuesto, su opinión.

Dan Pichelman
fuente
¿Es esta una opinión general, de otros autores y canas en el campo (tengo canas, en realidad)? Si es solo "la opinión de un reportero", puedo decirles a los estudiantes eso. Tengo, en realidad. Nunca he oído hablar de esta idea antes.
El género del autor es irrelevante, pero de todos modos actualicé mi respuesta. Prefiero seleccionar <o <=basarme en lo que es más natural para el problema particular que estoy resolviendo. Como otros han señalado, en un bucle FOR <tiene más sentido en lenguajes tipo C. Hay otros casos de uso que favorecen <=. Use todas las herramientas a su disposición, cuando y donde sea apropiado.
Dan Pichelman
Discrepar. No puede haber problemas de fiabilidad con tipos enteros: en C, tenga en cuenta: for (unsigned int i = n; i >= 0; i--)o for (unsigned int i = x; i <= y; i++)si ypasa a ser UINT_MAX. Oops, esos bucles para siempre.
jamesdlin
-1

Cada una de las relaciones <, <=, >=, >y también == y !=tienen sus casos de uso para comparar dos valores de coma flotante. Cada uno tiene un significado específico y se debe elegir el apropiado.

Daré ejemplos de casos en los que desea exactamente este operador para cada uno de ellos. (Sin embargo, tenga en cuenta las NaN).

  • Tiene una función pura costosa fque toma un valor de punto flotante como entrada. Con el fin de acelerar sus cálculos, decide añadir una memoria caché de los valores más reciente calculada, es decir, una asignación de tabla de consulta xa f(x). Realmente querrás usar ==para comparar los argumentos.
  • ¿Quieres saber si puedes dividir significativamente por algún número x? Probablemente quieras usar x != 0.0.
  • ¿Quiere saber si un valor xestá en el intervalo de la unidad? (x >= 0.0) && (x < 1.0)Es la condición correcta.
  • ¿Ha calculado el determinante dde una matriz y desea saber si es positiva definida? No hay razón para usar otra cosa que no sea d > 0.0.
  • ¿Quiere saber si Σ n = 1, ..., ∞ n −α diverge? Lo probaría alpha <= 1.0.

Las matemáticas de punto flotante (en general) no son exactas. Pero eso no significa que deba temerlo, tratarlo como magia, y ciertamente no siempre tratar dos cantidades de coma flotante iguales si están dentro 1.0E-10. Hacerlo realmente romperá tus matemáticas y hará que sucedan cosas extrañas.

  • Por ejemplo, si usa una comparación difusa con el caché mencionado anteriormente, introducirá errores graciosos. ¡Pero aún peor, la función ya no será pura y el error dependerá de los resultados calculados previamente!
  • Sí, incluso si x != 0.0y yes un valor finito de coma flotante, y / xno necesita ser finito. Pero podría ser relevante saber si y / xno es finito debido al desbordamiento o porque la operación no estaba matemáticamente bien definida para empezar.
  • Si tiene una función que tiene como condición previa que un parámetro de entrada xtenga que estar en el intervalo de la unidad [0, 1), estaría realmente molesto si disparara una falla de aserción cuando se llama con x == 0.0o x == 1.0 - 1.0E-14.
  • El determinante que ha calculado puede no ser exacto. Pero si va a pretender que la matriz no es positiva definida cuando lo es el determinante calculado 1.0E-30, no se gana nada. Todo lo que hizo fue aumentar la probabilidad de dar la respuesta incorrecta.
  • Sí, su argumento alphapodría verse afectado por los errores de redondeo y, por alpha <= 1.0lo tanto, podría ser cierto a pesar de que el verdadero valor matemático para la expresión alphase calculó podría haber sido realmente mayor que 1. Pero no hay nada que pueda hacer al respecto en este momento.

Como siempre en la ingeniería de software, maneje los errores al nivel apropiado y hágalos solo una vez. Si agrega errores de redondeo en el orden de 1.0E-10(este parece ser el valor mágico que usa la mayoría de la gente, no sé por qué) cada vez que compara cantidades de punto flotante, pronto encontrará errores en el orden de 1.0E+10...

5gon12eder
fuente
1
Una respuesta excelente, aunque es cierto, solo para una pregunta diferente a la que se hizo.
Dewi Morgan
-2

El tipo de condicional utilizado en un bucle puede limitar los tipos de optimizaciones que puede realizar un compilador, para bien o para mal. Por ejemplo, dado:

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

un compilador podría suponer que la condición anterior debería hacer que el ciclo salga después del enésimo paso a menos que n pueda ser 65535 y el ciclo pueda salir de alguna otra manera que no sea excediendo n. Si se aplican esas condiciones, el compilador debe generar un código que haga que el ciclo se ejecute hasta que algo más que la condición anterior lo haga salir.

Si el bucle se hubiera escrito como:

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

entonces un compilador podría asumir con seguridad que el ciclo nunca necesitaría ejecutarse más de n veces y, por lo tanto, podría generar un código más eficiente.

Tenga en cuenta que cualquier desbordamiento con tipos firmados puede tener consecuencias desagradables. Dado:

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

Un compilador podría reescribir eso como:

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;

Tal bucle se comportaría de manera idéntica al original si no se produce un desbordamiento en los cálculos, pero podría ejecutarse para siempre, incluso en plataformas de hardware donde el desbordamiento de enteros normalmente tendría una semántica de ajuste consistente.

Super gato
fuente
Sí, creo que eso está un poco más adelante en el libro de texto. No es una consideración todavía. Las optimizaciones del compilador son útiles para comprender, y se debe evitar el desbordamiento, pero en realidad no fue la motivación de mi pregunta, que fue más en términos de cómo las personas entienden el código. ¿Es más fácil leer x> 5 o x> = 6? Bueno, depende ...
A menos que esté escribiendo para un Arduino, el valor máximo para int será un poco más alto que 65535.
Corey
1
@Corey: el valor máximo para uint16_t será 65535 en cualquier plataforma donde exista el tipo; Usé uint16_t en el primer ejemplo porque esperaría que más personas conozcan el valor máximo exacto de un uint16_t que el de un uint32_t. El mismo punto se aplicaría en cualquier caso. El segundo ejemplo es agnóstico con respecto al tipo de "int", y representa un punto de comportamiento crítico que muchas personas no reconocen acerca de los compiladores modernos.
supercat