Aviso importante

Atención:
Debido a que carezco de conexión a internet, y la que uso es muy limitada y esporádica, la mayoría de aplicaciones no están actualizadas. Por desgracia las donaciones para mantener el blog o las aplicaciones que gratuitamente ofrezco son ridículas, y como las conexiones a internet y los servicios de operador son muy caros, ello me impide aportar nuevas incorporaciones y actualizar las aplicaciones existentes.

24.3.18

Redondeo de números en Delphi / Lazarus con strings

Delphi (y Lazarus) posee muchas funciones para redondear y truncar números, pero en muchos casos no funcionan del todo bien o no permiten tener el control sobre lo que queremos mostrar. Por eso, personalmente prefiero trabajar el redondeo con el string, es decir, el texto que tenemos tras realizar la operación aritmética que queramos llevar a cabo.

He buscado y no he visto ningún lugar que ofrezca el código para hacerlo, de manera que os voy a poner el que yo utilizo.




Como bien sabréis, para trabajar con operaciones matemáticas es mejor hacerlo sobre un tipo single que un integer, digamos que en la variable "h" tenemos el resultado de la operación matemática, sea éste cual sea, de esta forma:

i := ansipos(',',floattostr(h)); //tenemos la posición de la coma

"i" es una variable de tipo integer, en donde llevamos la posición de la primera coma que encontremos, que obtendremos mediante la función "ansipos". De esta forma, en "i" tenemos la posición de la coma y todo lo que venga detrás de ella será la parte decimal sobre la que trabajaremos.

Si queremos, por lo tanto, obtener los dos decimales tras la coma, pondremos (en un edit, en este caso):

edit1.text := Copy(floattostr(h), 1, i+2);

La función "Copy", como bien sabéis, extrae de un string todo lo que venga desde una posición hasta otra. En este caso, le estamos diciendo que nos traiga todo lo que haya desde el primer carácter, hasta dos carácteres tras la coma (o sea: i+2). Si quisiéramos tener tres decimales, simplemente usaríamos "i+3", y así sucesivamente (siempre y cuando vigilemos, y tengamos en cuenta, que el resultado va a dar tantos decimales claro, cosa que podemos hacer si usamos el mismo "ansipos" como delimitador, y "length" como útil recurso para obtener la extensión de la cadena).

Con ésto, en "edit1" tendremos la cantidad de número entero, y dos dígitos de la parte decimal, sin recurrir a funciones específicas de redondeo y pudiendo tener más control sobre el resultado.

Un problema con el que nos podemos encontrar es si el resultado no posee comas. En éste supuesto, al no contener la variable ningún valor (o si la inicializamos a cero, un cero), nos dará como resultado solo dos cifras. Lo veremos claramente con un ejemplo:

- Si el resultado de una operación es "4521", con este sistema nos saldrá "45", lo que a todas luces es inadmisible, obviamente.

Para solucionarlo, podemos hacer uso de nuevo de "ansipos". Esta función retornará un 0 en caso de no encontrar el carácter que buscamos, en caso contrario, o sea, de encontrarlo, retornará la posición de dicho carácter, lógicamente. Con esto, podemos utilizar la siguiente sentencia para evitar obtener resultados erróneos:

if i = 0 then
edit1.text := floattostr(h)
else
edit1.text := Copy(floattostr(h), 1, i+2);


Como veis, es bastante sencillo: si la variable "i" nos ha devuelto un cero, no hacemos nada, y mostramos el resultado tal cual, porque será un entero. Si ha devuelto un número diferente de cero, entonces aplicamos la función "Copy" que explicamos antes.

Otra mejora que podríamos implementar, si queremos rizar el rizo, es realizar una función para que se redondee al alza (o a la baja) según se necesite, haciendo uso también de "ansipos" y obteniendo únicamente el segundo carácter tras la coma. Una vez transformados en número, si éste es mayor o igual a 5, sumaremos un 1 al primer dígito, y transformaremos el segundo en un 0, y si es menor lo dejaremos tal cual. Vendría a ser una función similar a ésta:

if (x >= 5) then
i[2] := 0;
i[1] := i[1] +1


Obviamente aquí faltarían todo el sistema de conversión y manejo del array, y quizá sea más eficiente (y elegante) tratar ese número de una forma más pormenorizada en una función más depurada que ésta, pero solo lo pongo para que sea fácil de entender cómo sería su funcionamiento de una manera simple y sencilla.

Con este sistema no necesitaremos librerías de matemáticas ni llamadas a funciones raras, y podremos tener un control bastante ágil y específico sobre el resultado, manejando mucho mejor los decimales que con los recursos matemáticos, que a veces no funcionan todo lo bien que debieran.

| Redacción: Bianamaran.blogspot.com

3 comentarios:

  1. No recuerdo ahora los detalles, pero ese tipo de cosas me daban problemas en idiomas donde el separador decimal no es el que esperas.

    Sé que complica un poco más la lógica, pero es mejor asumir que la coma es lo que encuentres cuando no sea un número.

    Yo por rendimiento uso esto:
    unsigned long long __inline TfrmMain::ParseNumberThousand (String psNumber)
    {
    //return (StrToInt64Def(psNumber, FormatSettings.ThousandSeparator, ""), 0));
    TCHAR *acNumber, acRes[64];


    acNumber = psNumber.c_str();
    size_t iNumberLen = _tcslen(acNumber);

    unsigned int iResPos = 0;
    for (unsigned int iCount = 0; iCount < iNumberLen; iCount++)
    {
    //If it is a digit, we add it to the result
    if (_istdigit(acNumber[iCount]))
    {
    acRes[iResPos] = acNumber[iCount];
    iResPos++;
    }
    }
    acRes[iResPos] = NULL;
    return ((unsigned long long) _ttoi64(acRes));
    }

    Ves que está comentado el StrToInt64Def que era equivalente pero más lento. La gracia es que esa si que te permite especificar los formatos locales.

    ResponderEliminar
  2. Gracias Guti :D

    No obstante yo creo ver un "for", ya sabes que no uso "fors" :D

    A mí los microprocesadores me quieren mucho, no les hago contar.

    ResponderEliminar