sábado, 31 de mayo de 2014

Mejor no dar algo por sentado

Introducción


Sin darnos cuenta, nos acostumbramos a utilizar herramientas de cierto modo, y cuando utilizamos una similiar damos por hecho que funciona exactamente igual.

Por ejemplo, solemos dar por sentado que ciertas operaciones funcionan igual en un lenguaje de programación que en otro. Normalmente no hay problema porque suele ser así, pero es importante ser concientes de que no tiene por qué serlo:

a veces el funcionamiento de alguna característica de un lenguaje depende de la convención que se siguiera al diseñarlo y puede ser diferente de la convención seguida al diseñar otro lenguaje.

Un ejemplo real


Una vez escribí un programa en Ruby y al hacer pruebas obtenía resultados que no coincidían con lo esperaba.

Depurándolo acoté la zona del programa donde se realizaba mal un cálculo, pero por mucho que miraba el código no entendía lo que ocurría.

Hasta que descubrí que la aritmética en Ruby no funciona como yo suponía...

Estaba tan acostumbrado a utilizar la aritmética de C/C++ o Java, que no me esperaba que algo tan básico como dividir un entero entre otro no funcionase igual en Ruby. Pero vaya, así es.

Cuando se divide un número entero entre otro y el resultado no es un número entero, sino uno real (con decimales), hay que realizar un redondeo (o más bien un truncamiento). Pues resulta que no todos los lenguajes siguen la misma convención.

Al dividir un número negativo entre uno positivo o viceversa, en C/C++ y Java se trunca el resultado hacia cero (arriba), de modo que el valor absoluto del truncamiento de una división entera no depende del signo de los operandos. Por ejemplo:

OperaciónResultadoComentario
+5/3+1Resultado de truncar +1.667 hacia cero o -inf (abajo)
-5/3-1Resultado de truncar -1.667 hacia cero o +inf (arriba)

Sin embargo, en Ruby el truncamiento siempre es hacia abajo, variando el valor absoluto en función del signo de los operandos:

OperaciónResultadoComentario
+5/3+1Resultado de truncar +1.667 hacia cero o -inf (abajo)
-5/3-2Resultado de truncar -1.667 hacia -inf (abajo)

Ruby sigue una convenión distinta a la que siguen C++ y Java, por lo que se obtienen resultados diferentes.

Compruébalo tu mismo


Aquí hay unos pequeños programas que demuestran lo comentado en el apartado anterior: en Java, en C++ y en Ruby. Cada programa se puede copiar, pegar y probar online en el enlace indicado debajo.

public class RoundTest {

    public static void main(String[] args) {
        int a= -5;
        int b= 3;
        System.out.format("(%s/%s)=%s%n", a, b, (a/b));
        a= 5;
        b= 3;
        System.out.format("(%s/%s)=%s%n", a, b, (a/b));
    }
}

#include <cstdio>

using namespace std;

int main() {
    int a= -5;
    int b= 3;
    printf("(%d/%d)=%d\n", a, b, (a/b));
    a= 5;
    b= 3;
    printf("(%d/%d)=%d\n", a, b, (a/b));

    return 0;
}

#!/usr/local/bin/ruby -w

Integer a= -5;
Integer b= 3;
puts "(#{a}/#{b})=#{(a/b)}";
a= 5;
b= 3;
puts "(#{a}/#{b})=#{(a/b)}";

Con estos pequeños programas es fácil comprobar que los resultados en Ruby difieren de los obtenidos en C++ y Java.

Recuerda que hay más casos


Hay más lenguajes que implementan la división de forma similar a Ruby: Python, Tcl... También varía la implementación de la operacion Módulo (Modulus).

Un ejemplo mucho más típico y conocido son las sentencias condicionales multivía (case, when, switch...). Unos lenguajes, por ejemplo Pascal, tratan de forma exclusiva cada caso, de modo que se ejecuta exclusivamente el caso por el que se entra, mientras que otros, como C/C++ proporcionan un punto de entrada, pero debe ser el programador quien indique explícitamente donde interrumpir el flujo. de ejecución.

No sólo entre hay diferencia entre lenguajes de programación. Hay otras características que varían de unos sistemas a otros, como por ejemplo, el tratamiento que hace Oracle de las Strings vacías: las asimila a NULL.

Conclusión


Conviene tener el citado ejemplo en cuenta para recordar que al cambiar de un lenguaje de programación a otro, no se debe asumir que todo funciona exactamente igual.

Nunca está de más hacer alguna pequeña prueba para cerciorarse de que el programa se comporta como es de esperar.

Enlaces


Enlaces de interés relacionados con este artículo:

(Actualizado 31/05/2014)

Recapitulando: año 1

Repasando los artículos del blog escritos a lo largo de este año, puedes descubrir:


Si has leído alguno, muchas gracias y espero que te haya gustado.

Y sobre todo, agradezco especialmente a aquellos que han hecho algún comentario. El feedback siempre es bienvenido.

(Actualizado 31/05/2014)