Interactuando con esos enrevesados humanos

0
1758

Nosotros, los humanos, somos bastante buenos sacando el significado por contexto. Los ordenadores son terribles, hacen exactamente lo que les decimos que hagan. La diversión llega cuando les damos información incorrecta para formatear números.

[box style=»1″]

Éste artículo es una traducción al castellano de la entrada original publicada, en inglés, por Dr. Heinz Kabutz en su número 236 del JavaSpecialists newsletter. Puedes consultar el texto original en Javaspecialists’ Newsletter #240: Interfacing with Messy Humans

Este artículo se publica traducido en adictos, con permiso del autor, por David Gómez García, (@dgomezg)consultor tecnológico en Autentia, colaborador de JavaSpecialists e instructor certificado para impartir los cursos de JavaSpecialists.
[/box]

Interactuando con esos enrevesados humanos

Bienvenidos a la edición número 240 de The Javatm Specialists’ Newsletter, escrita desde Ljubljana, Slovenia. He impartido aquí mí Extreme Concurrency & performance course for Java 8 para Epilog, una pequeña compañía de ingeniería. Como hago siempre, pregunté a los alumnos cuánta experiencia tenían con Java. Algunos de ellos llevan programando en Java más tiempo que yo, así que estaba profundamente intimidado: ¿Cómo podría enseñarles nada nuevo?. Resultó que tuvimos unas buenísimas discusiones sobre algunos temas avanzados de Java y me satisfizo saber que me gané el sueldo 🙂

La última noche, su CEO nos llevó a visitar la ciudad y nos contó la historia de Ljubljana. Sí, ese es un beneficio adicional de trabajar con empresas pequeñas: Un tour privado con el CEO. ¿Sabíais que la rueda más antigua de Europa se descubrió en Ljubljana?. ¡Data de hace aproximadamente 5150 años! Es también la rueda de madera más antigua descubierta nunca. Buscadlo.


Interactuar con esos Locos Humanos

Es difícil programar las máquinas para que piensen como nosotros. Igual que las personas con síndrome de Asperger, se toman las cosas demasiado al pie de la letra. Tenemos que medir nuestras palabras con cuidado. Puede que piensen que realmente querías hacer «sudo rm -rf /«.

En 1984, mi padre compró un ZX Spectrum. Quería utilizarlo para hacer cálculos con una hoja de cálculo. En cuanto la hoja de cálculo se acababa de cargar desde una cinta de audio, y tras una larga espera, empezaba a introducir sus números con impaciencia. No funcionó. Cada número tenía un signo de interrogación parpadeando a su lado. No sabían cuál podía ser el error con sus números. En determinado punto, me pidieron que echase un vistazo. Como ya había hecho algo de programación en BASIC, se me ocurrió sugerir que utilizasen el punto decimal como separador decimal, en lugar de la coma. Eso solucionó el problema y a los 13 años, fue la primera vez que me encontré este problema. Pero no la última.

Oficialmente, en Sudáfrica, ciento veintitrés mil cuatrocientos cincuenta y seis Rands sudafricanos con setenta y ocho céntimos se escribe con el siguiente formato: «R123 456,78». El punto decimal es una coma y el separador de miles es un espacio. Pero no cualquier espacio: un espacio indivisible (U+00A0). Casi nadie utiliza el formato oficial. Escribimos «R123,456.78», igual que en Estados unidos o Reino Unido. Esto es así para banca y compras por internet, documentos gubernamentales, contratos, etc…. Pero podrías encontrarte cualquiera de estos formatos «R123456.78», «R123456,78», «R123 456.78», «R123 456,78» o «R123,456.78». Nuestros cerebros humanos de lógica difusa ni siquiera perciben que hay diferencias. Lo analizamos con lo que más sentido nos parece que tiene.

Dado que tenemos tantas opciones, es responsabilidad de cada persona elegir cuál es el que prefiere ver. Personalmente me gusta «R123 456.78», pero a veces lo escribo como «R123’456.78». Nunca utilizo la coma como separador decimal. Y tampoco lo hace ninguno de mis bancos en Internet. Mis auditores me envían los extractos financieros con un espacio como separador de miles, agradable a la vista. En sus facturas utilizan el punto como separador decimal. Incluso aunque la coma es el separador decimal en el formato oficial, casi nadie lo usa.

En la Europa continental es diferente. Aquí todo el mundo usa la coma como separador decimal. Hace unos años intenté pagar 502.46 libras esterlinas a mis impresores en Inglaterra a través de banco local por internet. Después de haber enviado el pago, recordé que había olvidado enviarles el justificante de pago. Cuando revisé la transacción comprobé que… ¡el portal de banca online había ignorado el punto!. ¡Dios mío! Afortunadamente pude cancelar rápidamente la transferencia.

Pese a llevar viviendo en Europa 10 años, sigo utilizando en_ZA como locale en mi máquina, porque tiene como formato de fecha el genial yyyy/MM/dd. No obstante, me he dado cuenta de que algunas actualizaciones de Mac OS X actualizan de manera que mis puntos decimales vuelven a verse como comas. En la «configuración avanzada» de mi máquina, tuve que personalizar mi configuración en_ZA para utilizar un formato más familiar como #,###.##. Así los números en mi aplicación vuelven a verse bien. El único peligro son esos bancos europeos por internet.

Un divertido juguete con el que he jugado últimamente es un torno Carbide3D Nomad CNC. Por supuesto, lo controlo desde Java. Preparé un pequeño programa que permitiría al torno seguir el cursor del ratón en mi pantalla y comenzar a perforar en el momento en que pulsase el botón del ratón. Es divertido ver la cabeza del torno seguir mis movimientos, como un perro obediente. La forma de interactuar con el torno CNC es a través de GCode, un antiguo lenguaje de control basado en texto para indicar qué cortar. No tan antiguo como la rueda de Ljubljana, pero casi. Conecté todo y ejecuté mi programa. La máquina tartamudeo y empezó a girar fuera de control, intentando perforar la mesa, volando a izquierda y derecha, arriba y abajo, a toda velocidad. Tiré del cable de alimentación para desenchufarlo. Por los pelos…, pero ¿qué es lo que estaba mal?.

Había preparado con cuidado todo mi GCode usando String.format("%.3f") para asegurarme de que tenía el número perfecto de puntos decimales en los comandos. Sin embargo, por error ¡había ejecutado mi código con JDK9 Early Access!. Sip, utilizar una version EA de Java con un equipo potencialmente peligroso para tallar madera no parece muy inteligente. Pero, al tiempo que jugaba con el torno CNC, estaba también revisando el código de la JDK en Java 9, y en concreto cómo se usa VarHandles en las clases atómicas y de concurrencia. Había olvidado volver a configurar mi entorno con Java 8.

Con la ayuda de Stuart Marks y Ben Evans, descubrimos que Java 9 utiliza ahora los formatos Unicode «oficiales» para los puntos decimales. Para Sudáfrica, eso significa que todos tus números decimales se convertirán automáticamente en «123 456,78» en el futuro, te guste el formato o no. Recuerda que ya había personalizado el formato en mi máquina a un «123,456.78» mucho más común. Mi error con String.format() estaba en que asumí que descartaría la información de localización. Lo mejor es especificar siempre el locale que necesitas, usando String.format(Locale.US, "%.3f", num).

Definitivamente, es un bug descartar las personalizaciones de usuario. He dado de alta el bug de Java y en menos de 24 horas, lo cerraron como «not an issue». Lo es. Afecta a un país con 50.000.000 o 50,000,000 o 50 000 000 habitantes. Ya sé que es «sólo África» y además, «No tienen ordenadores allí» </sarcasmo>. Microsoft hizo algo parecido imponiéndonos esa configuración oficial (la que no usa nadie), pero también aceptaron las configuración de usuario. Si vives en Sudáfrica y esto te afecta, ¿quizá quieras también notificar el bug?

Para ver la regresión en Java 9, echa un vistazo a este código:

	import java.text.*;
	import java.util.*;

	public class PayPrinters {
	  public static void main(String... args) throws Exception {
	    Locale en_ZA = new Locale("en", "ZA");
	    NumberFormat format = NumberFormat.getInstance(en_ZA);
	    System.out.printf(en_ZA, "%,.2f%n", 123456.78);
	    parse(format, "123 456,78"); // normal space
	    parse(format, "123 456.78"); // normal space
	    parse(format, "123\u00a0456,78"); // no-break space
	    parse(format, "123\u00a0456.78"); // no-break space
	    parse(format, "123456,78");
	    parse(format, "123456.78");
	    parse(format, "123.456,78");
	    parse(format, "123,456.78");
	  }

	  private static void parse(NumberFormat format, String number)
	      throws ParseException {
	    System.out.println("parse(\"" + number + "\") = " +
	        format.parse(number));
	  }
	}
	

Este es el resultado con varias versiones de Java que tengo en mi Mac OS X. El número se formatea como 123,456.78 en Java 6, 7, y 8, pero en Java 9 aparece como 123 456,78, ignorando por completo mi configuración. Utilizar un espacio indivisible es arriesgado. No lo puedes ver. Y la mayoría de la gente usará un espacio normal, en cuyo caso el número tampoco se podrá parsear.

	heinz$ java -showversion PayPrinters
	java version "1.6.0_65"
	Java(TM) SE Runtime Environment (build 1.6.0_65-b14-468-11M4833)
	Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-468, mixed mode)

	123,456.78
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123456,78") = 12345678
	parse("123456.78") = 123456.78
	parse("123.456,78") = 123.456
	parse("123,456.78") = 123456.78
	heinz$ java -showversion PayPrinters
	java version "1.7.0_80"
	Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
	Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

	123,456.78
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123456,78") = 12345678
	parse("123456.78") = 123456.78
	parse("123.456,78") = 123.456
	parse("123,456.78") = 123456.78
	heinz$ java -showversion PayPrinters
	java version "1.8.0_101"
	Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
	Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

	123,456.78
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123456,78") = 12345678
	parse("123456.78") = 123456.78
	parse("123.456,78") = 123.456
	parse("123,456.78") = 123456.78
	heinz$ java -showversion PayPrinters
	java version "9-ea"
	Java(TM) SE Runtime Environment (build 9-ea+134)
	Java HotSpot(TM) 64-Bit Server VM (build 9-ea+134, mixed mode)

	123 456,78
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123 456,78") = 123456.78
	parse("123 456.78") = 123456
	parse("123456,78") = 123456.78
	parse("123456.78") = 123456
	parse("123.456,78") = 123
	parse("123,456.78") = 123.456

Hay un workaround que descubrió Stuart Marks. Si arrancas la JVM con ‑Djava.locale.providers=HOST,CLDR,JRE utiliza primero tu configuración, después el Unicode oficial (CLDR) para tu locale y por último, la configuración de la JRE. En mi opinión, éste debería ser el valor por defecto para las JVMs. Aquí veis su salida en Java 9:

	heinz$ java -Djava.locale.providers=HOST,CLDR,JRE \
	    -showversion PayPrinters
	java version "9-ea"
	Java(TM) SE Runtime Environment (build 9-ea+134)
	Java HotSpot(TM) 64-Bit Server VM (build 9-ea+134, mixed mode)

	123,456.78
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123 456,78") = 123
	parse("123 456.78") = 123
	parse("123456,78") = 12345678
	parse("123456.78") = 123456.78
	parse("123.456,78") = 123.456
	parse("123,456.78") = 123456.78

Gracias por leer este artículo 🙂 Breve y bonito, que mi vuelo de vuelta a casa está a punto de salir.

Saludos.

Heinz.

Heinz es el creador de "The Java Specialists' Newsletter".
Doctor en Informática, Heinz ha participado en el desarrollo de grandes aplicaciones en Java, ha formado a miles de desarrolladores profesionales y es ponente habitual en las principales conferencias sobre Java.
Reconocido como Java Champion por Sun Microsystems -los creadores de Java- por su trabajo en mejorar Java, Heinz imparte cursos de JavaSpecialists.eu por todo el mundo, de forma remota y presencial. Es autor de dichos cursos, incluidos 'Java Specialists Master', 'DesignPatterns and Concurrency Specialists' y 'Performance and Concurrency for Java 8'.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad