Java Para Cálculos Numéricos

A veces se utiliza Java para hacer cálculos numéricos intensivos a pesar de su fama de ser lento. Muchas veces la rapidez de cálculo es un aspecto importante, pero casi nunca el único a la hora de elegir el lenguaje para implementar estos cálculos. Java tiene muchas cosas atractivas para este tipo de aplicaciones como el hecho de generar programas portables, de tener muy buen soporte para programación paralela y tener «garbage collector» entre otros. Pero, ¿qué tan lento o rápido es realmente para este tipo de cálculos?

Para tener una idea decidí comparar Java con C que es el lenguaje rápido por excelencia aunque en este campo muchos consideran a Fortran como en rey de la computación numérica.

Para la comparación tomé el algoritmo de Chudnovsky, que calcula el número PI con la cantidad de dígitos que se quiera, mientras se disponga de memoria suficiente para almacenarlos.

Para el cálculo en C usé la biblioteca GMP que permite hacer cálculos con precisión arbitraria y se autoproclama como la implementación más rápida del cálculo de PI.

En Java usé la biblioteca Apfloat que también ofrece la posibilidad de hacer cálculos con precisión arbitraria y que promete ser más rápida que la implementación de BigDecimal de Java para números grandes.

En esta era de computadoras con múltiples núcleos, opté por realizar el cálculo utilizando todo el paralelismo disponible y para ello utilicé dos implementaciones paralelizadas del algoritmo en cuestión. Apfloat ya trae una implementación paralela del algoritmo de Chudnovsky como ejemplo de uso y la implementación de C está basada en OpenMP.

  • Algoritmo de Chudnovsky en C
  • Algoritmo de Chudnovsky en Java
  • Opciones de compilación de C: GCC: 4.7.2  -fopenmp -Wall -O2 -lgmp -lm
  • Versión de GMP 5.0.2
  • Versión de Apfloat 1.7.1
  • Versión Java: OpenJDK 1.7.0u15
  • Hardware: AMD Phenom X4 9550 4 GB RAM
  • Sistema operativo: Ubuntu 12.10 64 bits
  • Memoria disponible para Java 2 GB

Parámetros usados para Apfloat

  • builderFactory=org.apfloat.internal.LongBuilderFactory
  • defaultRadix=10
  • cacheL1Size=131072
  • cacheL2Size=524288
  • cacheBurst=64
  • memoryTreshold=402653184
  • sharedMemoryTreshold=268435456
  • blockSize=1048576
  • numberOfProcessors=4
  • filePath=
  • fileInitialValue=0
  • fileSuffix=.ap
  • cleanupAtExit=true

Resultados

Dígitos Apfloat (segundos)
GMP (segundos) Apfloat/GMP
100.000 0,435 0,041 10,61
1.000.000 3,987 0,595 6,70
10.000.000 67,821 8,044 8,43
100.000.000 745,732 111,824 6,67

Los resultados muestran que en esta prueba la versión Java llega a ser 6 veces más lenta que GMP.

Como Apfloat utiliza almacenamiento en disco cuando los números son más grandes que un cierto tamaño (configurable), elegir la configuración adecuada lleva un poco de pruebas. La configuración por omisión no está del todo optimizada para computadoras actuales con varios gigas de RAM.

Código de la Prueba

El código C es el citado más arriba.

Java

package calculatepi;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatRuntimeException;
import org.apfloat.samples.Operation;
import org.apfloat.samples.Pi;
import org.apfloat.samples.PiParallel;

public class App {

    public static void main(String[] args) {
        final int max = Integer.parseInt(args[0]);
        Writer nullWritter = new Writer() {
            @Override
            public void write(char[] chars, int i, int i1) throws IOException {
            }

            @Override
            public void flush() throws IOException {
            }

            @Override
            public void close() throws IOException {
            }
        };

        try (
                PrintWriter out = new PrintWriter(nullWritter);
                PrintWriter err = new PrintWriter(new File("/home/user/apfloatPierr.txt"))) {
            Pi.setOut(out);
            Pi.setErr(err);
            final Operation warmUpOp = new PiParallel.ParallelChudnovskyPiCalculator(1000000, 10);

            final List<Operation> ops = new ArrayList<>();

            for (int i = 100000; i <= max; i = i * 10) {
                ops.add(new PiParallel.ParallelChudnovskyPiCalculator(i, 10));
            }

            for (int i = 0; i < 5; i++) {
                PiParallel.run(1000000, 10, warmUpOp);
            }
            int digits = 100000;
            for (Operation op : ops) {
                err.println("====================================");
                PiParallel.run(digits, 10, op);
                digits = digits * 10;
            }

        } catch (IOException | ApfloatRuntimeException ex) {
            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}

Bash

#!/bin/sh

HEAP=2g
OUTFILE=apfloatPierr.txt
rm $OUTFILE

echo "Java..."

java -Xmx$HEAP -Xms$HEAP -jar /home/user/CalculatePi.jar 100000000
sleep 1

echo "C..."
echo "100000" >> $OUTFILE
./pi 100000 0 4 1> /dev/null 2>> $OUTFILE
echo "1000000" >> $OUTFILE
./pi 1000000 0 4 1> /dev/null 2>> $OUTFILE
echo "10000000" >> $OUTFILE
./pi 10000000 0 4 1> /dev/null 2>> $OUTFILE
echo "100000000" >> $OUTFILE
./pi 100000000 0 4 1> /dev/null 2>> $OUTFILE

Entorno de Desarrollo de C/C++ para Windows

Instalación de Cygwin

Cygwin es una colección de herramientas de GNU para Windows. Entre las herramientas de desarrollo nos interesa el compilador GCC, el debugger GBD y demás herramientas necesarias para compilar como MAKE.

Ir a http://www.cygwin.com/ y bajar el instalador de Cygwin (http://www.cygwin.com/setup.exe).
cygwin-1

Ejecutar setup.exe y seguir la instalación.


Si se está en una computadora con conexión a Internet, se debe elegir la opción “Install from Internet”.

En la carpeta llamada Root Directory se van a instalar las herramientas que seleccionemos.

En la carpeta seleccionada quedarán los paquetes que descarguemos para una futura reinstalación o para copiarlos a otra computadora e instalarlos en ella.


Se debe elegir desde dónde descargar los paquetes. Si se elige un servidor muy lento o que no funciona, se puede cambiar por otro.

Muchos paquetes ya están seleccionados, pero además se deben marcar para instalar los siguientes de la categoría devel: automake1.9, cppcheck, gcc4, gdb, make, libmpfr4




Configurar Path

Finalizada la instalación se debe agregar la carpeta donde están las herramientas al Path de Windows. Yendo a Panel de ControlSystem and SecuritySystem y eligiendo Advanced system settings.

En la solapa Advanced, seleccionar el botón Environment Variables….

Se debe crear o editar una variable de usuario seleccionado en botón New… o Edit… debajo del recuadro User variables for….
Si la variable Path ya existe, se la edita y si no existe, se la crea nueva.

En caso de crearla, se le pone el nombre Path y como valor el nombre de la carpeta elegida para instalar Cygwin. Si la variable Path ya existía, simplemente al modificar su valor agregar al final un punto y coma y luego el nombre completo de la carpeta: C:\path\anterior;C:\Otro\Path\anterior;C:\cygwin\bin

Instalación de un Editor

Para escribir nuestros programas sirve cualquier editor que esté hecho para programar. Típicamente estos editores resaltan la sintaxis del lenguaje y permiten mostrar números de línea, entre otras cosas. Gedit es uno de ellos. El bloc de notas de Windows no es recomendable.

Está disponible para Windows.

Su instalación es muy sencilla.







Uso de las Herramientas

Cygwin crea un entorno UNIX completo dentro de Windows. Este incluye, además de las herramientas de programación que instalamos, un shell. Para usar el compilador y las herramientas de Cygwin debemos hacerlo mediante ese shell al que accedemos ejecutando Cygwin Terminal. El directorio “activo” inicialmente al abrir Cygwin Terminal está ubicado dentro de la carpeta donde lo instalamos. Podemos situar nuestros programas ahí o en cualquier otra parte y movernos hacia esa otra carpeta usando los comandos del shell. En la imagen se observa un programa guardado en la carpeta “c”.

Para compilar y ejecutar el programa que escribimos, debemos abrir Cygwin Terminal. No es lo mismo abrir el shell de Windows “cmd” ya que Windows no soporta algunas de las características que tiene UNIX como los links simbólicos y es probable que tengamos errores extraños al ejecutar las herramientas de Cygwin.

En este ejemplo para compilar y ejecutar el programa que escribimos y guardamos debemos ejecutar estos comandos:

  • cd c
  • gcc -o helloworld -Wall -std=c99 helloworld.c
  • ./helloworld

Instalación sin acceso a Internet

Para instalar en una computadora sin conexión a Internet se debe primero ejecutar el instalador en otra donde sí se disponga de conexión y utilizar la opción
Download Without Installing. Luego proseguir con la instalación. El instalador va a descargar los paquetes seleccionados en una carpeta local. Esa carpeta puede ser copiada a otra computadora que no tenga conexión a Internet.

En la computadora sin conexión a Internet se debe ejecutar el instalador y seleccionar la opción Install From Local Directory.

Luego se debe indicar en qué carpeta se copiaron los paquetes descargados anteriormente y proseguir con la instalación.

Aritmética Binaria y Decimal

Por Qué Nunca Usar float y double para Representar Montos de Dinero

Cuando aprendemos a programar una de las primeras lecciones es que se usan diferentes tipos de datos para representar números enteros y números no enteros. Una variable entera no puede tener el valor 0,5. En Pascal se usan los tipos Integer y Real y en C están por ejemplo los tipos int, long, float y double.

Supongamos que tenemos que hacer un sistema de permita administrar sumas de dinero; más específicamente, deudas y pagos. Puede ser para un banco, una tarjeta de crédito, un almacén o cualquier aplicación comercial. Para dicho sistema tenemos que hacer una función en C muy sencilla que reciba la deuda actual y un arreglo de pagos que realizó el deudor y que devuelva la deuda actualizada una vez descontados los pagos. Este podría ser el prototipo:

double actualizarDeuda(double deuda, double pagos[], int cantPagos);

Como la deuda puede ser con centavos, no podemos usar int para representarla entonces usamos double.

Lo que hace la función es restar de la deuda cada elemento del arreglo pagos y devolver el monto actualizado.

double actualizarDeuda(double deuda, double pagos[], int cantPagos){
    double deudaActualizada = deuda;
    int i;
    for(i = 0; i<cantPagos;i++){
        deudaActualizada -= pagos[i];
    }
    return deudaActualizada;
}

Vamos a constatar que la función hace lo esperado con una prueba.

Supongamos que un deudor tenía una deuda de $308,21 y realizó 4 pagos por $8,01; $0,20; $299,99 y $0,01. Si bien no es sencillo pagar en efectivo montos como $8,01, es un caso que puede darse perfectamente en transacciones electrónicas.

double deuda = 308.21d;
double pagos[4] = {8.01d, 0.20d, 299.99d, 0.01d};

En teoría la deuda está saldada así que la función debería devolvernos que la deuda es $0,00.

double miDeuda = actualizarDeuda(deuda, pagos, 4);
if(miDeuda == 0.0d){
    printf("Libre deuda.\n");
} else {
    printf("Al Veraz.\n");
}

Sin embargo la prueba imprime Al Veraz. es decir que nuestro sistema sigue registrando una deuda a pesar de que está saldada.

La nueva deuda no es $0,00, pero casi. El valor es $-0,00000000000000909516. Es decir, nuestra función nos calculó que el deudor pagó 9 milbillonésimas de peso más de lo que debía.

Esto nos hace pensar que hay un error de redondeo así que decidimos usar la función round.

miDeuda = round(actualizarDeuda(deuda, pagos, 4));
if(miDeuda == 0.0d){
    printf("Redondeando, logro el libre deuda.\n");
} else {
    printf("Redondeando, igual voy al Veraz.\n");
}

Ahora sí obtenemos el resultado Redondeando, logro el libre deuda lo que es en principio correcto.

A pesar de que en muchos sistemas el problema de la precisión está “solucionado” de esa manera, no se está atacando la causa del problema sino una de sus consecuencias. Como se observa a continuación, redondear el resultado puede llevarnos a dar por saldada una deuda que todavía no está totalmente paga.

pagos[2]-=0.4d;
miDeuda = round(actualizarDeuda(deuda, pagos, 4));
if(miDeuda == 0.0d){
    printf("Con redondeo, debiendo $0,40 igual logro el libre deuda.\n");
} else {
    printf("A pesar de redondear, como debo voy al Veraz.\n");
}

Si descontamos $0,40 de uno de los pagos, está claro que no deberíamos extender el libre deuda. Sin embargo, obtenemos el cartel Con redondeo, debiendo $0,40 igual logro el libre deuda lo que es incorrecto y le hace perder dinero quien nos encargó hacer el sistema. Es una situación complicada para nosotros ya que desde el punto de vista del acreedor, es una atrocidad.

Este problema lo tenemos en cualquier lenguaje de programación y también lo tenemos en muchas aplicaciones muy usadas para hacer cálculos con montos de dinero.

arit-excelLa única solución que ofrecen al problema en el caso de esa planilla de cálculo tan famosa es “redondear” (http://support.microsoft.com/kb/214118/es).

La causa del problema está en la representación binaria de los números de coma flotante. Como no todos los números son representables se tiene que buscar el número más cercano al especificado y eso implica un error en la representación. Por ejemplo el número 0,11 no es representable como double (porque en binario es periódico) y es aproximado con 0,11000000000000000056.

Los números de coma flotante están definidos por el estándar IEEE-754 que data de 1985. A partir de 2008, el estándar incorporó además de los números de binarios de coma flotante que ya existían (que usan base 2 como los float y double) nuevos números decimales de coma flotante (que usan base 10).

Esa es la solución al problema de la precisión que estamos teniendo. Vamos a reescribir la función de esta forma:

_Decimal64 actualizarDeuda(_Decimal64 deuda, _Decimal64 pagos[],int cantPagos){
    _Decimal64 deudaActualizada = deuda;
    int i;
    for(i = 0; i<cantPagos;i++){
        deudaActualizada -= pagos[i];
    }
    return deudaActualizada;
}

Usamos el tipo _Decimal64 en lugar de double. El resto es igual.

Y ahora sin aplicar ningún redondeo, las cuentas simplemente dan el resultado esperado.

_Decimal64 deuda = 308.21dd; 
/* el sufijo dd es el que corresponde al tipo _Decimal64 */
_Decimal64 pagos[4] = {8.01dd, 0.20dd, 299.99dd, 0.01dd};
_Decimal64 miDeuda = actualizarDeuda(deuda, pagos, 4);
if(miDeuda == 0.0dd){
    printf("Con _Decimal64, obtengo en libre deuda normalmente.\n");
} else {
    printf("Con _Decimal64, al Veraz.\n");
}

El soporte de decimales de coma flotante está disponible a partir de GCC 4.2, pero no está disponible para todas las plataformas. En particular en Windows con Cygwin obtenemos un error que dice “error: decimal floating point not supported for this target”.

Otros compiladores sí lo soportan en Windows como por ejemplo el de Intel. http://software.intel.com/en-us/articles/using-decimal-floating-point-with-intel-c-compiler/.

Más información

  1. http://en.wikipedia.org/wiki/IEEE_754-2008
  2. http://speleotrove.com/decimal/

Acceder a Properties Según el Contexto con Spring y Tomcat

Cuando una aplicación web se instala en diferentes servidores suele ser necesario modificar ciertos parámetros como direcciones de otros servidores (por ejemplo de correo), parámetros de conexión a la base de datos o hasta parámetros internos de la aplicación como cantidad de usuarios admitidos o nivel de log.

En un clarísimo artículo intitulado 6 Tips for Managing Property Files with Spring ese es uno de los temas: tener diferentes valores para ciertas properties según en qué ambiente o contexto está ejecutándose la aplicación. Una de las posibles soluciones es tener varios archivos llamados por ejemplo

  • database-prod.properties
  • database-test.properties
  • database-desa.properties

Cada archivo tendrá las mismas properties con los valores acordes a cada ambiente (producción, desarrollo y testing en el ejemplo). Lo que tenemos que hacer es que se lea el archivo correcto según el ambiente.

La clave es Spring que tiene un mecanismo muy flexible para acceder a properties mediante el PropertyPlaceholderConfigurer o el PropertySourcesPlaceholderConfigurer. Esos beans hacen el trabajo pesado; nosotros sólo tenemos que configurarlos según nuestras necesidades.

Una configuración típica de ese bean es algo así

 <context:property-placeholder
        location="classpath:database.properties"
        system-properties-mode="OVERRIDE"
        ignore-unresolvable="true"/>

Teniendo muchas versiones del archivo database.properties, la configuración quedaría de esta forma

 <context:property-placeholder
        location="classpath:database-${ambiente}.properties"
        system-properties-mode="OVERRIDE"
        ignore-unresolvable="true"/>

La intención es que ${ambiente} se reemplace por “desa”, “test” o “prod”. Para lograrlo hay que hacer estos pasos:

1) Definir en el archivo server.xml del Tomcat una variable de entorno dentro de GlobalNamingResources

  <Environment
            name="ambiente"
            value="desa"
            type="java.lang.String"
            override="false"/>

2) Definir en el elemento Context en el archivo META-INF/context.xml de la aplicación web esta referencia a la variable que creamos en el punto anterior

  <ResourceLink
      global="ambiente"
      name="ambiente"
      type="java.lang.String"/>

De esa forma Spring va a leer el archivo properties según el valor definido en el server.xml.

Industria Argentina = Marcador Negro

MADE IN CHINA tapado con marcador negro

MADE IN CHINA tapado con marcador negro (click para agrandar)

Esta es una foto del envoltorio de los auriculares que trajo mi teléfono Samsung “Industria Argentina”. Como se ve, en Tierra del Fuego le estamos pagando el sueldo a una persona para que use un marcador negro y así “crear” industria nacional.
No muy claramente, pero se alcanza a ver que lo tapado es “MADE IN CHINA”.

¿Qué lleva a tachar con marcador la leyenda “MADE IN CHINA”? ¿Se trata de mantener las formas? ¿Que no sea tan evidente la mentira de que en Tierra del Fuego se fabrican productos electrónicos?

El esquema de promoción industrial de Tierra del Fuego se instauró en la década del 70, cuando la industria electrónica era algo novedoso y pocos preveían lo que pasaría 40 años después cuando casi todo elemento de la vida cotidiana tiene componentes electrónicos. Hoy la electrónica es fundamental para la vida diaria, cosa que hace 30 años era bien diferente.

Es un régimen cerrado en el que hay aproximadamente diez empresas favorecidas y ninguna otra puede entrar. Al extremo de que las empresas de teléfonos (como Motorola, Samsung y RIM) tuvieron que hacer convenios con alguna de las que ya estaban para empezar a “fabricar” sus teléfonos en los últimos años.

Es un esquema que sólo favorece a las afortunadas empresas y que perjudica a todos los demás. Se basa en que el estado fije aranceles de importación, impuestos internos, cuotas o embargos a los productos electrónicos importados. De esa forma los encarece o directamente impide su importación. Así crea una ventaja para estas empresas que pueden importar los teléfonos, tabletas, televisores o notebooks desarmados sin pagar esos aranceles para luego armarlos dentro del país y venderlos a un precio menor al del producto importado. Eso es todo lo que hacen. Sólo tienen que fabricar la caja de cartón (y aparentemente tachar las referencias a MADE IN CHINA con marcador).

Típicamente el negocio es así:

Si un teléfono importado cuesta $1.000, las empresas de Tierra del Fuego hacen lobby para que el estado le aplique impuestos que lo encarezcan a $2.000. Luego importan el teléfono desarmado, lo arman y lo venden a $1.800.

De esa forma todos menos las 10 empresas pierden.

  • Los consumidores que tienen que pagar más caros los productos o directamente no pueden acceder a ellos a ese nuevo precio.
  • Se reduce la variedad de productos, ya que en general sólo se venden aquellos que estas empresas deciden fabricar.
  • Se perjudica la balanza comercial, ya que según ellos mismos dicen

“Irónicamente, el precio del total de los componentes para producir cualquier producto electrónico es apenas más alto que el del producto terminado hecho en China.”

Eso implica que importando el teléfono listo para la venta gastaríamos 100 dólares de la balanza comercial, en lugar de los 107 dólares que cuesta traer las partes.

  • Se perjudican los vendedores minoristas que venden menos aparatos electrónicos (a mayor precio, menos demanda)

Este esquema se ha repetido con todos los gobiernos, ya que son empresas con un gran poder de lobby. Al obtener estos beneficios pueden tomar empleados y le dan un pretexto gigante a los políticos para justificar los beneficios que reciben. Los perjuicios se pasan por alto.

Firefox 16 Facilita La Lectura en Android

La última versión de Firefox para Android permite optimizar la página que se está visualizando para ser leída con mayor comodidad.

Además permite guardar la página para leerla luego sin necesidad de estar conectado, se puede cambiar el color de fondo y sincronizar las páginas guardadas mediante Sync. Próximamente estará también disponible en la versión de escritorio.

Verificando Afirmaciones de Bichos de Campo

La conversación de los precios empieza a partir del minuto 10.

En el programa del 5 de octubre de 2012 los Bichos de Campo compararon precios de un supermercado español con supermercados argentinos. La conclusión que sacaron es que los productos cuestan mucho más en Argentina. En realidad están equivocados. Su error es tomar el tipo de cambio oficial que es fijado artificialmente bajo por el Banco Central de Argentina. La única forma de comparar precios en diferentes monedas en si el tipo de cambio lo fija el mercado libremente y no un funcionario en base a motivaciones políticas. Si hacemos la misma cuenta que hicieron lo Bichos tomando el valor del Euro en el mercado libre, vemos que llegamos a una conclusión totalmente opuesta.

Euro Bichos: $6,00
Euro Libre: $8,1223 (fuente: http://dolarblue.net/)

Comparación de Precios entre Argentina y España

Comparación de Precios entre Argentina y España

Tomé el precio más bajo (entre Coto y Disco) de Argentina para cada producto. Al contrario de lo que dicen los Bichos de Campo, la mayoría son más baratos en Argentina. Si suponemos que compramos todos los productos y sumamos los precios, nos da que gastamos casi lo mismo en España y en Argentina (sólo un 1,32% de ahorro en Argentina).

Seguramente de existir un mercado libre de cambios, el valor del Euro estaría algo por debajo de los $8,12, aunque seguramente no tan bajo como los $6,00 que fija el gobierno.

En conclusión, es la política del gobierno la que introduce estas distorsiones en los precios al fijar el tipo de cambio.