jueves, 21 de febrero de 2013

Detalles en el formateo de decimales con toString en C#

O por que sale una , en vez de . cuando uso toString("0000.00")

Si esta programando una interfaz grafica en C# muy probablemente habra necesitado hacer un textBox que formatee los datos en el, ya sabe agregando ceros a la izquierda y poniendo los decimales.

En C# basta con aprovechar el evento "Leave" para que, al momento que el cuadro de texto (referido de aquí en adelante como TextBox) lea el contenido, se convierta en numero y luego usar .toString("00000.00") para dar el formato necesario, fácil, ¿no?

Pues NO

Al menos en C# existe un detalle capaz de causar errores impredecibles, calculos errados  y un dolor de cabeza buscando que salio mal, y cuya única pista parece ser que el textBox esta ignorando el punto decimal.

Todo esto resultado de una buena idea que tal  vez podría estar o mejor explicada o mejor implementada.

Ejemplo en acción.

Para explicar esto usaremos un programa muy sencillo, simplemente recibe un numero  en un textbox y al pulsar un boton calcula y muestra el cuadrado de este en otro textBox  como muestra la figura 1

Figura 1 - Aplicación de Ejemplo.
Y que al poner un numero lo formatee de modo que ponga los ceros a la izquierda y la derecha necesarios, como  muestran la Figuras 2 y 3

Figura 2 - Entrada de Datos.
Figura 3 - Formato de Datos.
(Ya en la figura 3 puede empezar a notar que algo anda mal, llegare a eso en un momento)

Ese formateo se logra con el evento Leave del textBox, asumire que ya sabe como declararlo, el codigo que use  para dar formato es el siguiente:

        private void textBox1_Leave(object sender, EventArgs e)
        {
            double d = Convert.ToDouble(textBox1.Text);

            textBox1.Text = d.ToString("0000.00");
        }

        
y para el botón se usa este código, de nuevo nada del otro mundo

        private void button1_Click(object sender, EventArgs e)
        {
            double d = Convert.ToDouble(textBox1.Text);
            d = d * d;
            textBox2.Text = d.ToString("0000.00");
        }


Suena simple pasamos el texto a double y luego ese double lo pasamos a texto aplicandole el formato deseado, incluso puede  ver que pusimos un punto para indicar donde iran los decimales.

Como puede ver en la figura 2 se puso un 45.5 en el textBox1, pero al pulsar fuera del textoBox1 vemos el 45.5 convertise  en un 0455.00 como en la figura 3 y al pulsar el boton btenermos el cuadrado de ese numero, pero *no* del que pusimos Figura 2

Figura 4 - Resultado erroneo.
La unica pista que tenemos sale de la documentacion de .NET donde se refieren a un parametro del metodo toString de tipo CultureInfo
el cual es la clave de lo que esta pasando aqui.

Al parecer, con el propósito de facilitar la localización de los programas, C# permite indicar como se deben separar los números, y otros valores  dependiente de localidad esto es recordando que en algunos idiomas la , se usa (quien sabe por que) para indicar decimales y esto se puede demostrar en el  programa ejemplo pues si escribe 45,5 en el programa (Figura 5), consigue el resultado correcto (Figura 6). 

Figura 5 - 45,5
 
Figura 6 - Resultado usando comas.

Pero como aquí *no* se usa la coma para los decimales que el programa se comporte de ese modo es molesto, por decir de menos , sin mencionar que puede ser  desconcertante que el programa *no* este haciendo lo que se le indica en el codigo.

Por fortuna la solucion es sencilla basta agregar al inicio del programa la instrucción

using System.Globalize;

y el parametro "CultureInfo.InvariantCulture" al toString y al  Convert.toDouble y listo, el punto decimal hará honor a su nombre y funcionara se espera.

Figura 7 - Programa corregido

Figura 8 - Comportamiento correcto.

Para hacer esto mas claro aquí esta el código con las correcciones, el parámetro "CultureInfo.InvariantCulture" se encarga de indicar a la función de hacer exactamente lo que se le dice, sin tomar en cuenta detalles sobre el idioma del sistema.

private void button1_Click(object sender, EventArgs e)
{
    double d = Convert.ToDouble(textBox1.Text,   CultureInfo.InvariantCulture);
    d = d * d;
    textBox2.Text = d.ToString("0000.00", CultureInfo.InvariantCulture);

}


private void textBox1_Leave(object sender, EventArgs e)
{
    double d = Convert.ToDouble(textBox1.Text, CultureInfo.InvariantCulture);
    textBox1.Text = d.ToString("0000.00", CultureInfo.InvariantCulture);
}


Espero que esto sea de ayuda para los que se encuentren con este tipo de problemas.