viernes, 27 de enero de 2012

last, como obtener los últimos inicios de sesión

Linux
El comando last es una pequeña utilidad que nos permite conocer cuando se inició la sesión y cuando finalizó, con que usuario y desde que dirección IP se realizaron los últimos inicios de sesión a un sistema. Quizá esta información no sea de utilidad en alguna situación o para detectar intrusiones a un sistema. Su uso es muy simple:

$ last -F -10

Este comando nos permite conocer los ultimos 10 inicios de sesión en el sistema. Si queremos conocer los inicio de sesión realizados por un determinado usuario lo haríamos con:

$ last -F -10 root

Este comando tiene unos cuantos parámetros más que pueden consultarse en la página man del comando.

last

sábado, 21 de enero de 2012

Errores de precisión, redondeo y representación con float y double

Java
Los tipos de datos float y double aunque muy usados y útiles tienen defectos, como que no son capaces de representar el resultado de ciertas operaciones ariméticas entre números reales con total precisión, en algunos casos solo son capaces de representar aproximaciones a ellos y que las operaciones las realizan en base dos. Al igual que nosotros no podemos repsentar 1/3 con números en base diez con total exactitud (solo una aproximación 0,333333333... con treses hasta el infinito) a las computadoras les pasa otro tanto de lo mismo, aunque estas guardan los números en base dos. Por tanto usarlos para hacer ciertas operaciones aritméticas, y siendo acumuladas varias de ellas, puede dar lugar a errores de redondeo y precisión en el resultado final. Por estos motivos usar estos tipos de datos no son los más adecuados para trabajar en una aplicación que calcule precios. Veámoslo con un ejemplo.

Supongamos que tenemos una cantidad de dinero tal que 100,05 a la que aplicamos un 10% de descuento y posteriormente un 5% en concepto de impuestos. Las líneas de código que calculan esto son:

import java.text.NumberFormat;

double amount = 100.05;
double discount = amount * 0.10;
double total = amount - discount;
double tax = total * 0.05;
double taxedTotal = tax + total;

NumberFormat money = NumberFormat.getCurrencyInstance();
System.out.println("Subtotal: "+ money.format(amount));
System.out.println("Discount: " + money.format(discount));
System.out.println("Total: " + money.format(total));
System.out.println("Tax: " + money.format(tax));
System.out.println("Tax+Total: " + money.format(taxedTotal));

Y el resultado es:

Subtotal: 100,05 €
Discount: 10,00 €
Total: 90,04 €
Tax: 4,50 €
Tax+Total: 94,55 €

Si nos fijamos en la suma de total más impuestos hay una diferencia de 0.01 y si presentamos un desglose de precios como este a un usuario puede que este piense que hay algún error en el cálculo, le genere desconfianza y no haga la compra en el peor de los casos. Viendo los valores sin los redondeos que hace NumberFormat tenemos:

Subtotal: 100.05
Discount: 10.005
Total: 90.045
Tax: 4.50225
Tax+Total: 94.54725

Los redondeos que hace NumberFormat es HALF_EVEN por defecto, de modo que cuando un decimal está equidistante a las dos partes se redondea a la parte par por lo que con una precisión de dos decimales:

Discount: 10.005 se redondea a 10.00
Total: 90.045 se redondea a 90.04
Tax: 4.50225  se redondea a 4.50
Tax+Total: 94.54725 se redondea a 94.55

En este caso se trata de un problema de redondeo pero ahora supongamos que tenemos una cantidad de 0,70 céntimos a la que no aplicamos un descuento pero si el procentaje de impuestos del 5%. Tendríamos:

import java.text.NumberFormat;
double amount = 0.70;
double tax = amount * 0.05;
double taxedTotal = tax + amount;

NumberFormat money = NumberFormat.getCurrencyInstance();
System.out.println("Subtotal: "+ money.format(amount));
System.out.println("Tax: " + money.format(tax));
System.out.println("Tax+Total: " + money.format(taxedTotal));

Subtotal: 0,70 €
Tax: 0,03 €
Tax+Total: 0,74 €

Nos encontramos otra vez con la diferencia de 0.01. Vemos los valores sin redondear por NumberFormat:

Subtotal: 0.7
Tax: 0.034999999999999996
Tax+Total: 0.735

Aquí se ve que el resultado de ciertas operaciones aritméticas entre datos double (o float) son almacenadas por una computadora con errores de precisión, 0.70 * 0.05 (debería ser 0.035).

Para evitar estos errores debemos utilizar la clase BigDecimal que pemite almacenar números con una precisión en la práctica infinita en base 10, realizar los cálculos como los humanos esperan, en base diez, y hacer los redondeos de precisión. Aplicando una precisión de dos decimales a los números y usando BigDecimal tenemos:

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;

RoundingMode RM = RoundingMode.HALF_EVEN;
BigDecimal amount = new BigDecimal("100.05"); 
BigDecimal discountPercent = new BigDecimal("0.10");
BigDecimal discount = amount.multiply(discountPercent).setScale(2, RM); 
BigDecimal total = amount.subtract(discount).setScale(2, RM);
BigDecimal taxPercent = new BigDecimal("0.05");
BigDecimal tax = total.multiply(taxPercent).setScale(2, RM);
BigDecimal taxedTotal = total.add(tax).setScale(2, RM);
NumberFormat money = NumberFormat.getCurrencyInstance(); 
System.out.println("Subtotal : " + money.format(amount));
System.out.println("Discount : " + money.format(discount));
System.out.println("Total : " + money.format(total)); 
System.out.println("Tax : " + money.format(tax)); 
System.out.println("Tax+Total: " + money.format(taxedTotal));

Ahora los precios si están correctos:

Subtotal : 100,05 €
Discount : 10,00 €
Total : 90,05 €
Tax : 4,50 €
Tax+Total: 94,55 €

Para el otro caso en el que teníamos un error de precisión:

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;

RoundingMode RM = RoundingMode.HALF_EVEN;
BigDecimal amount = new BigDecimal("0.70");
BigDecimal taxPercent = new BigDecimal("0.05");
BigDecimal tax = amount.multiply(taxPercent).setScale(2, RM);
BigDecimal taxedTotal = tax.add(amount).setScale(2, RM); 

NumberFormat money = NumberFormat.getCurrencyInstance(); 
System.out.println("Subtotal: "+ money.format(amount)); 
System.out.println("Tax: " + money.format(tax)); 
System.out.println("Tax+Total: " + money.format(taxedTotal));

Subtotal: 0,70 €
Tax: 0,04 €
Tax+Total: 0,74 €

Referencia:
http://www.javamexico.org/blogs/luxspes/por_que_usar_bigdecimal_y_no_double_para_calculos_aritmeticos_financieros
http://speleotrove.com/decimal/decifaq1.html#tzeros
http://www.mkyong.com/java/how-do-calculate-monetary-values-in-java-double-vs-bigdecimal/
http://blogs.oracle.com/CoreJavaTechTips/entry/the_need_for_bigdecimal
http://stackoverflow.com/questions/7539/please-explain-the-use-of-java-math-mathcontext/7561#7561
http://en.wikipedia.org/wiki/Floating_point

domingo, 1 de enero de 2012

Calendario Arch Linux 2012

Empieza un nuevo año y una de las cosas que primero hacemos es ver y obtener un calendario para poner encima de nuestra mesa. Hace unos días vi un calendario tematizado con Ubuntu. En un rato que he sacado he tenido tiempo de crear uno basado en Arch Linux y traducido al español. Así que aquí lo tenéis listo y esperando para imprimir, construirlo y ponerlo encima de vuestra mesa. En el paquete tenéis la versión SVG, PNG y PDF (200 KiB) lo podéis obtener mediante este enlace.

Calendario Arch Linux 2012


Si le cogeís gusto a la papiroflexia los modelos de papel tal vez os interese obtener este Cheat Cube tambén para nuestra distribución. Ahora Tux y el cheat cube van a tener un nuevo compañero :). En cuanto pueda imprimirlo le saco unas fotos.

¡Que empecéis bien el año!

Referencia:
Calendarios de ubuntu para imprimir del 2012
http://spreadubuntu.org/es/node/751