Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   redondear decimas de un float como excel. (https://www.clubdelphi.com/foros/showthread.php?t=69833)

ingabraham 12-09-2010 22:01:16

redondear decimas de un float como excel.
 
hola
tengo un numero real 9,975

mi funcion en delph
Código Delphi [-]
roundto(numeroreal,-2)
=9,97

en excel me da= 9,98 osea cuando es 5 lo sube
y en delphi lo baja

que funcion puedo utilizar en delphi para esto
gracias,

Caral 12-09-2010 22:29:09

Hola
Esto redondea pero no se si es lo que buscas.
Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
var a,b: Real;
begin
  a:= StrToFloat(Edit1.Text);
  b:= Round(a);
  Edit2.Text:= Format('%8.2n',[b]);
end;
Saludos

ecfisa 13-09-2010 00:53:28

Hola.

Agrego otra posibilidad a la expuesta por el amigo Caral.
Código Delphi [-]
function RedondeaPorExceso(Numero: Double; Digitos: Integer): Double;
var
  m: Double;
begin
  m:= Exp(Ln(10) * Digitos);
  Result:= Round(Numero * m + 1/(m + 5)) / m;
end;

Saludos.

Caral 13-09-2010 01:19:47

Hola
Muy interesante funcion amigo.
Creo que deberías de mostrar un ejemplo de su uso, recuerda que no todos sabemos lo mismo y que muchos pueden tener la misma duda.
A algunos nos cuesta mas que a otros entender funciones y su comportamiento.
No esta de mas el ejemplo amigo.
Saludos

Casimiro Notevi 13-09-2010 01:23:29

Creo que también puede valer esto:

Si quires redondear a dos decimales:

resultado := redondeo(importe,2);

Código Delphi [-]
uses Math;

...
...

function Redondeo (valor: Double; decimales: integer = 0): Double;
var
  factor: Double;
begin
  factor := IntPower(10,decimales);
  //
  if valor > 0 then
    Result := (trunc((valor*factor)+0.5)) / factor
  else
    Result := (trunc((valor*factor)-0.5)) / factor;
end;

ecfisa 13-09-2010 02:08:34

Cita:

Empezado por Caral (Mensaje 376151)
Hola
Muy interesante funcion amigo.
Creo que deberías de mostrar un ejemplo de su uso, recuerda que no todos sabemos lo mismo y que muchos pueden tener la misma duda.
A algunos nos cuesta mas que a otros entender funciones y su comportamiento.
No esta de mas el ejemplo amigo.
Saludos

Oigo y obedezco la sugerencia... :)

La función es muy similar a la de Casimiro. La diferencia es que él conocia una función que yo no: IntPower... :o
Por lo que usé dos funciones matemáticas estandard para lograrla.
Con ellas obtenemos un número que nos sirve para ajustar la cantidad de dígitos con que queremos redondear. Se almacena
en la variable local 'm' de la función RedondeaPorExceso. Luego para redondear por exceso debemos sumar 5 en esa posición.

Por último la llamada es como cualquier función:
Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
const
   PI= 3.14159265358979323846;
var
  n: Double;
  i: Integer;
begin
  // Ej 1:
  Memo1.Clear;
  for i:= 1 to 10 do
    Memo1.Lines.Add(FloatToStr(RedondeaPorExceso(PI,i)));
  // Ej 2:
  n:= RedondeaPorExceso(PI,5);
  // Ej 3:
  ShowMessage(Format('%8.5f %8.3f',[n,RedondeaPorExceso(PI,3)]));
end;

Saludos.

ecfisa 13-09-2010 02:35:50

Hola.

Veo que por hacer las cosas en el aire, cometí un error al escribir la función. Y aunque añade un dígito en la posición deseada, para que este sea 5, debería ser:
Código Delphi [-]
function RedondeaPorExceso(Numero: Double; Digitos: Integer): Double;
var
  m: Double;
begin
  m:= Exp(Ln(10) * Digitos);
  Result:= Round(Numero*m+5/m)/m; // Así no: Result:= Round(Numero * m + 1/(m + 5)) / m;
end;

Saludos.:)

Caral 13-09-2010 02:40:50

Hola
Muy buena explicacion amigo.
Ahora hasta yo la puedo entender.:)
Saludos

Héctor Randolph 13-09-2010 04:37:54

Hola a todos

Aunque ya está resuelto, solo quiero comentar otra forma de llegar al mismo resultado. Existe la función SetRoundMode en la unidad Math.

Esto es lo que pone la ayuda:

Cita:

Description

Call SetRoundingMode to specify how the FPU handles rounding issues. The rounding mode can be any of the following values:

Value Meaning

rmNearest Rounds to the closest value.
rmDown Rounds toward negative infinity.
rmUp Rounds toward positive infinity.
rmTruncate Truncates the value, rounding positive numbers down and negative numbers up.
Entonces puedes usarla de esta forma:

Código Delphi [-]
 SetRoundMode(rmUp);
 Memo1.Lines.Clear;
 Memo1.Lines.Add(FloatToStr(RoundTo(9.975,-2)));

Saludos

Casimiro Notevi 13-09-2010 11:18:56

Hay tantas formas de hacer una misma cosa... cada uno tiene su método/código/truco que al final llevan a la solución. Depende de la imaginación (y conocimientos) de cada uno para lograrlo :)

ingabraham 13-09-2010 22:51:40

muchas gracias a todos,
sus respuestas han sido certera.
haber como finalizo este hilo

rrf 14-09-2010 02:16:54

Hola a todos/as.

El tema del redondeo me ha preocupado en varias ocasiones.

El problema que tengo es cuando aparece un valor como 0.75444448 y lo quieres redondear a 3 decimales.

Lo normal en todas las funciones que he visto (incluidas las que he revisado en este hilo) es que, para redondear a 3 decimales, se utilizan 4 decimales y, en este ejemplo (el valor 0.75444448), el redondeo daría 0.754 .

Lo que es muy correcto, pero que no aprovecha la totalidad de decimales disponibles.

Si se aprovecharan todos los decimales para redondear, el valor final debería dar 0.755 .

Todo ello redondeando con:
Código Delphi [-]
SetRoundMode( rmUp ) ;

Como el tema no se resuelve en las funciones de otras personas que he visto, me he animado a hacer una función en la que se indique lo que es normal, el valor decimal y el número de decimales a los cuales hay que redondear; pero añadiendo otro parámetro: el número de decimales desde el que se empieza a redondear de forma decreciente.

En el ejemplo, el valor 9.97444756, se redondea a 3 decimales y se empieza a calcular el redondeo desde los 7 decimales (luego se hará con 6, luego con 5, ... hasta que se llegue a 3).

He utilizado recursión y es de la primeras veces que lo hago; así que puede haber algún error, aunque las pruebas que hice salieron bien.

Este es el código:

Código Delphi [-]
uses math ;
.......
.......

function TForm1.Redondea2(DD: double; Max_Dig, Num_Dig: integer): double;
begin
    // Redondea el valor DD
    // a un total de Num_Dig dígitos decimales
    // partiendo en el redondeo desde el dígito decimal Max_Dig
    //----------------------------------------------------------

  if Max_Dig >= Num_Dig
    then result := Redondea2( RoundTo( DD, - Max_Dig ), Max_Dig -1, Num_Dig )
    else result := DD ;

end;

procedure TForm1.btn5Click(Sender: TObject);
begin

  SetRoundMode( rmUp ) ;
  Label1.Caption := FloatToStr( Redondea2( 9.97444756, 7, 3) ) ;

end;

Lo incluyo por si esto le es útil a alguien.

Saludos.

Delphius 14-09-2010 04:19:42

Cita:

Empezado por rrf (Mensaje 376308)
Hola a todos/as.

Lo normal en todas las funciones que he visto (incluidas las que he revisado en este hilo) es que, para redondear a 3 decimales, se utilizan 4 decimales y, en este ejemplo (el valor 0.75444448), el redondeo daría 0.754 .

Efectivamente, el redondeo se aplica según el valor del dígito siguiente. Es decir que si se desea redondear el valor a 3 decimales, este último decimal se verá afectado según el valor del 4to decimal. Por ejemplo, siguiendo tu caso:

0.75444448

El 4 en negrita es el número a redondear. El 4 subrayado es el dígito a evaluar con el modo de redondeo. Dependiendo del modo se verá afectado hacia arriba, abajo, trunca, etc. Fuera del modo, todo se resume a 3 posibilidades para ese 4: Se "convierte" en 3, 4 o 5.

Así es que se aplica el redondeo. La última cifra significativa de interés al redondeo se ve afectada por la siguiente.

Cita:

Empezado por rrf (Mensaje 376308)
Hola a todos/as.
(...)pero que no aprovecha la totalidad de decimales disponibles.

Si se aprovecharan todos los decimales para redondear, el valor final debería dar 0.755 .

Ese criterio de redondeo no lo veo demasiado exacto y de utilidad. Es un redondeo fuera de lo normal. Me cuesta ver cómo de 0.75444448 se puede llegar a ese resultado.

Cita:

Empezado por rrf (Mensaje 376308)
Todo ello redondeando con:
Código Delphi [-]SetRoundMode( rmUp ) ;




Recuerda volver al modo de redondeo anterior, de otro modo todos tus cálculos se verán afectados (inflados) y el error tras unas cuantas operaciones será mayor del que te esperas. ;)

Cita:

Empezado por rrf (Mensaje 376308)
Como el tema no se resuelve en las funciones de otras personas que he visto, me he animado a hacer una función en la que se indique lo que es normal, el valor decimal y el número de decimales a los cuales hay que redondear; pero añadiendo otro parámetro: el número de decimales desde el que se empieza a redondear de forma decreciente.

En el ejemplo, el valor 9.97444756, se redondea a 3 decimales y se empieza a calcular el redondeo desde los 7 decimales (luego se hará con 6, luego con 5, ... hasta que se llegue a 3).

Déjame ver si entendí... ¿Es decir empiezas a redondear cada dígito significativo hasta llegar a los decimales a considerar?

De ser así, disculpame pero creo que no es una buena idea, porque de ese modo se está incrementando el error tras cada avance. Todo comienza con un error de 0.5 ulp, después de moverse al nuevo dígito será de un máximo de 0.5 + 10, luego 0.5 + 10 + 100... y así se irá propagando hasta llegar al dígito en cuestión.
Esto en términos absolutos.

Imagínate que luego a ese resultado debes aplicar la siguiente operación:

Valor2 = Valor1 x 10000;

Hagamos de cuenta, como dices que debería ser 0.755 y no 0.745. Si hacemos dicha multiplicación se obtiene 7550 y 7450. La diferencia entre ambos es de 100, ahora multiplícalo por 100000... 75500 y 74500, diferencia: 1000.

Cuanto más sigas avanzando, más alta será la diferencia, los cálculos se harán más erróneos.

No he probado tu código. Pero si hace lo que me temo, creo que no es buen método de redondeo.

Saludos,

Casimiro Notevi 14-09-2010 12:00:50

Estoy con Delphius, el modo propuesto por el amigo rrf está bien como ejercicio, pero no es práctico ni vale para el "mundo real", ya me imagino las caras de los clientes protestando por esos "redondeos" tan "cuadrados" :)

Creo que la función que he propuesto sí es válida para "la vida real":

function Redondeo (valor: Double; decimales: integer = 0): Double;

Indicas el valor a "redondear" y el número de decimales al que quieres el redondeo.

ecfisa 14-09-2010 19:26:51

Hola.

Recién leo el hilo nuevamente...

Y coincido cien por ciento con Delphius.
Un error que por defecto o exceso ajustemos en diezmilésimas, podrían convertirse en centesimas, por ejemplo.

Saludos.

Delphius 14-09-2010 20:31:16

Cita:

Empezado por Casimiro Notevi (Mensaje 376342)
Estoy con Delphius, el modo propuesto por el amigo rrf está bien como ejercicio, pero no es práctico ni vale para el "mundo real", ya me imagino las caras de los clientes protestando por esos "redondeos" tan "cuadrados" :)

Creo que la función que he propuesto sí es válida para "la vida real":

function Redondeo (valor: Double; decimales: integer = 0): Double;

Indicas el valor a "redondear" y el número de decimales al que quieres el redondeo.

Exacto. No es como naturalmente medimos y trabajamos. Y además, es inútil intentar medir más allá de la escala que nos brinda las herramientas y a la que estamos interesados de aceptar como precisión. Esto se dice muy a menudo en las clases de cálculo.

Si uno acepta que sus mediciones serán precisas a 4 decimales, por dar un número, entonces sus mediciones no podrán ser mucho más precisas más allá de 5 decimales. Ergo... no tiene sentido continuar buscando más precisión. Por ejemplo:

Estoy midiendo la altura de un andamio con un metro convencional (al centímetro)... y justo la marca está entre los 3,20 y 3,21. No tiene sentido práctico que pretenda medir hasta las milésimas si la cinta métrica no tiene esa unidad de medida. ¿Que valor tomo? ¿3,20 o 3,21? Según esta regla, ... no tengo otra elección... o asumo 3,20 ó 3,21. No puedo asegurar si es 3,205 o 3,209, 3,201... o cualquier otro número que esté en el rango [3,20; 3,21]

Es por ello que una vez finalizada la medición, se adopta un criterio de redondeo.
Y Por más preciso que sean los cálculos el valor exacto podría (y estará) en un margen de +- 0,5 ulp.
Hagamos de cuenta que me consigo un metro con escala al milímetro... llego a que mide 3,203. El valor más preciso y exacto estará en el rango [3,2025; 3,2035].

Cita:

Empezado por ecfisa (Mensaje 376391)
Hola.

Recién leo el hilo nuevamente...

Y coincido cien por ciento con Delphius.
Un error que por defecto o exceso ajustemos en diezmilésimas, podrían convertirse en centesimas, por ejemplo.

Saludos.

Exacto. Y como dije, ir redondeando desde más decimales a menos es una técnica que irá acumulando el error.

Creo que mis explicaciones se ilustrará mejor con una regla:

Código:

+|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||+
|        |        |        |    *    |        |        |
0                  1                  2                  3
+-----------------------------------------------------------+

El * indica el número más exacto que podemos medir a esa escala. Subamos (o mejor dicho, bajemos) de decimales... Ahora en esta nueva escala tenemos dos opciones de redondeo:

Código:

+-|-|-|-|-+-|-|-|-|-+-|-|-|-|-+-|-|-|-|-+-|-|-|-|-+-|-|-|-|-+
|        |        |        |  * *  |        |        |
0                  1                  2                  3
+-----------------------------------------------------------+

Sigamos... veamos como ahora el efecto de redondeo nos va alejando:

Código:

+---------+---------+---------+---------+---------+---------+
|        |        |        |        |        |        |
0                  1        *        *                  3
+-----------------------------------------------------------+

Y por último:

Código:

+-------------------+-------------------+-------------------+
|                  |                  |                  |
0                  *                  *                  3
+-----------------------------------------------------------+

Como se aprecia, cada vez el rango del error absoluto se ve incrementado debido a los redondeos por exceso y por defecto.

Quizá para ciertos números el algoritmo que implementado rrf no provoque demasiada perturbaciones, pero en lo general no es óptimo. Pruébese con decimales como: ,1919191919... o ,91919191... o incluso com estos: ,454545... y ,54545454... ;)

Creo que con ello queda más que evidente el porqué es inútil asumir decimales y redondeos más allá de lo que estamos dispuestos a tolerar.

Saludos,

Delphius 14-09-2010 20:48:29

Ha... por cierto, recomiendo que se de una revisión a la unidad Math... Hay una buena cantidad de funciones, para lo que se ha estado debatiendo y viene al caso están las funciones: Round, RoundTo, SimpleRoundTo, además de Ceil y Floor y la posibilidad de indicar el modo de redondeo.

Amigo Casi, tu función me recuerda mucho a SimpleRoundTo.;)

Código Delphi [-]
function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var
  LFactor: Double;
begin
  LFactor := IntPower(10, ADigit);
  Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;

Por último, no olvidarse que en el mundo de la aritmética flotante no existe exactitud absoluta ;). Por más exactamente redondeadas que sean las implementaciones de las operaciones soportadas por el estándar IEEE 754 estamos sujetos a ciertas imprecisiones y limitaciones.

Saludos,

rrf 14-09-2010 21:09:47

Hola de nuevo.

Oye Delphius, gracias por tu explicación. No he hecho pruebas, pero parece que tienes razón y además, las demás opiniones te apoyan.

Cambiando de tema, creo que tienes habilidades para dedicarte a la docencia (y va en serio).

Ahora sí, amplío la información sobre esa función. He trabajado con el cálculo de % de una serie de valores. Redondeo a 2 decimales, pero es frecuente que la suma de los resultados finales sea un 100,01%, 100,02%, 99,99% ó 99,98%.

Lo que me parece un error "demasiado grande". Pero, por más que hice pruebas, no logré mejorar mucho los resultados.

Finalmente me quedó la idea de que posiblemente si hacía un redondeo como el que propuse en mi comentario anterior, la situación mejoraría.

No llegué a hacer la función hasta que leí este hilo en el foro. "Sobre la marcha", hice las pruebas y añadí el comentario. En la práctica, no lo probé mas que con un par de ejemplos.

Sin embargo, después de leer tu comentario Delphius y el de Casimiro y Ecfisa, creo ver que seguramente es un planteamiento equivocado.

Gracias por las aclaraciones y salu2.

Delphius 14-09-2010 21:35:50

Cita:

Empezado por rrf (Mensaje 376412)
Hola de nuevo.

Oye Delphius, gracias por tu explicación. No he hecho pruebas, pero parece que tienes razón y además, las demás opiniones te apoyan.

De nada. Pues yo si probé la función que pusiste. Al principio sospeché que era yo el que estaba equivocado y que al final podría resultar pero cuando empecé a mirar más adentro, y probar con los números extremos noté como se hacía pedazos el algoritmo.
Si aplica un redondeo razonable para ciertos números es una casualidad, pero en lo general... lo siento pero no eso no vale.

Si me apoyan es que les he dado una coima :D jajaja. No es que me apoyen ciegamente, ellos seguramente saben mucho más de lo que puedo saber yo... además ellos ya venían probando y proponiendo.
Nomás es que yo me animé a darle un pequeño giro al asunto.

Cita:

Empezado por rrf (Mensaje 376412)
Cambiando de tema, creo que tienes habilidades para dedicarte a la docencia (y va en serio).

No eres el primero en decírmelo... ¡hasta el sodero opina que debería ser docente/profesor!
Hechando a perder dicen que se aprende... el asunto es que si enseño estaré hechando a perder a algunos jóvenes y futuros profesionales.

Debo reconocer que todavía me lo pregunto si vale la pena y me encantaría... el asunto es si los aguanto (y ellos a mí, que es lo principal).

Cita:

Empezado por rrf (Mensaje 376412)
Ahora sí, amplío la información sobre esa función. He trabajado con el cálculo de % de una serie de valores. Redondeo a 2 decimales, pero es frecuente que la suma de los resultados finales sea un 100,01%, 100,02%, 99,99% ó 99,98%.

Lo que me parece un error "demasiado grande". Pero, por más que hice pruebas, no logré mejorar mucho los resultados.

Como dije en mi último post: la aritmética de punto flotante no es 100% exacta, más bien es aproximada. Tiene que ver no sólo el tipo de datos (y su precisión), sino también el modo de redondedo, los algoritmos, y en el modo de trabajar internamente.

El formato IEEE 754 impone cierto formato y los números deben adaptarse a éste. Por empezar hay un límite de dígitos significativos a almacenar en los tipo Simple, Double y Extended. Esto hace que los números no pueden ser más precisos de esa cantidad de dígitos. En segundo lugar no todos los números son exactamente representables en el formato por lo que la máquina en realidad almacena un número aproximado (como por ejemplo, 0.1). Y peor aún en los números irracionales... como Pi, la constante de euler... donde los efectos son más notables: no sólo se ven limitados por la precisión sino que no son representables. Otros números que tienen problemas son los números periódicos... 1/3 es 0,333333....

Y por último, el estándar IEEE 754 establece que las operaciones sean redondeadas. En síntesis, son muchas cosas que afectan y por ello nunca se conseguirá una total exactitud. Es por ello que cuando se trata de operaciones con números flotantes se estila trabajar con márgenes de error.

No quisiera extenderme demasiado, busca entre los foros "Lo que todo informático debe saber sobre aritmética flotante". He hablado antes al respecto. O mejor aún te hago llegar el enlace a material de consulta.

Creo que con ello tienes para darte una mejor idea del tema.

Cita:

Empezado por rrf (Mensaje 376412)
Finalmente me quedó la idea de que posiblemente si hacía un redondeo como el que propuse en mi comentario anterior, la situación mejoraría.

Pues, ya vez que no;)

Cita:

Empezado por rrf (Mensaje 376412)
No llegué a hacer la función hasta que leí este hilo en el foro. "Sobre la marcha", hice las pruebas y añadí el comentario. En la práctica, no lo probé mas que con un par de ejemplos.

Sin embargo, después de leer tu comentario Delphius y el de Casimiro y Ecfisa, creo ver que seguramente es un planteamiento equivocado.

Gracias por las aclaraciones y salu2.

Gracias nuevamente.
No tienes que agradecerme. Aquí se aprende todos los días, y te lo digo yo que me falta mucho por aprender :). No te creas que se mucho de mucho, nomás es que hago un esfuerzo para aparentar que se mucho:p

Saludos,

Casimiro Notevi 14-09-2010 22:06:46

Cita:

Empezado por Delphius
[..]Amigo Casi, tu función me recuerda mucho a SimpleRoundTo.;)

Código:

function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var  LFactor: Double;
begin
  LFactor := IntPower(10, ADigit);
  Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;



:eek::eek::eek: hasta el nombre de las variables se parecen, esa función la hicimos entre mi compañero y yo, un día en la que estábamos desesperados con los redondeos y dijimos "de hoy no pasa, hacemos una función que redondee de verdad". Y nos salió esa que he puesto.
Seguramente los de delphi se copiaron de nosotros :D


La franja horaria es GMT +2. Ahora son las 20:01:09.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi