Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   No entiendo el uso de decimales (https://www.clubdelphi.com/foros/showthread.php?t=73399)

darkerbyte 18-04-2011 20:56:10

No entiendo el uso de decimales
 
Hola amigos.

He tenido un problema (no se si ocurrira solo con D7 que es con el que trabajo) ya que cuando trabajo con numeros flotantes tiende a agregarme decimales. No le habia dado importancia hasta q mi cliente protestó porque cuando el hace una factura, la guarda y la recupera las cantidades no coinciden entre la factura que ha impreso y la que esta guardada.

Ejemplifico:

Ahorita estoy debuggeando un programa en el cual yo escribo por ejemplo 8.52 en un DecimalSpin. Al acceder a su propiedad "value" para obtener el numero me agrega digitos

Código Delphi [-]
cantidad := spin_cant.value; {Escribi 3, obtengo 3}
precio := spin_precio.value;  {Escribi 8.52, obtengo 8.5217390006}
total := cantidad * precio; 
{De esta manera me regresa 25.5652170018
al momento de ponerlo en la factura utilizando un FormatFloat obtengo 25.57
pero si hacemos la multiplicacion original obtenemos 25.56 que es lo correcto}

Como pueden notar el error es de solo 0.01 pero en una factura con 10 o 20 productos hacen variaciones de 10 a 20 centavos.

Pensé que el problema podría ser el Control, sin embargo me repite el mismo problema con cualquier otro control para decimales.
De hecho hice otro experimento

Código Delphi [-]
var
 s1,s2 : string;
 tmp : single;
begin
 precio := spin_precio.value;  
 s1 := FormatFloat('###########0.00;-###########0.00;0',  precio);
 s2 := FormatFloat('###########0.00;-###########0.00;0',  cantidad);
 //Ahora si, en s2 tengo 8.52
 tmp := StrToFloat(s1) * StrToFloat(s2);
 //Otra vez, en lugar de obtener 25.56 obtengo 25.559999416
end;

No entiendo el motivo de este comportamiento con los decimales. Asi que les pido tengan la gentileza de darme un poco de ayuda para corregir este problema.

Muchas gracias de antemano.

mcs 18-04-2011 21:05:34

Es porqué trabajas con variables de coma flotante (float o reals). Para evitar estos problemas de redondeo, se debe usar siempre variables de tipo Currency (que es coma fija).

Casimiro Notevi 18-04-2011 21:42:13

Cita:

Empezado por mcs (Mensaje 397520)
Es porqué trabajas con variables de coma flotante (float o reals). Para evitar estos problemas de redondeo, se debe usar siempre variables de tipo Currency (que es coma fija).

No solamente variables, obviamente, los campos de la base de datos también.

darkerbyte 19-04-2011 21:32:38

woW
 
A eso iba exactamente mi siguiente duda, ya que en MySQL me ocurria lo mismo. Ya estuve leyendo un poco y veo que para MySQL se debe especificar la columna como DECIMAL(M,D) pero me confunde un poco los parametros.

Para moneda cual seria la precision recomendada??
Por ahora estoy utilizando DECIMAL(4,2)

Gracias nuevamente, aprecio mucho su ayuda

Ñuño Martínez 27-04-2011 10:55:36

Un poco de off-topic y de SPAM: Hace un tiempo escribí un artículo titulado El Valor de un Céntimo, que está relacionado con el tema del manejo de valores fraccionarios en computadoras digitales. Seguro que te clarifica las cosas.

Delphius 27-04-2011 15:42:07

Hola,

Lecturas obligadas:
1. What Every Computer Scientist Should Know About Floating-Point Arithmetic
2. Comparing floating point numbers
3. Floating point numbers

Creo que con ello uno ya se hace la idea.

Saludos,
PD: A quien no le guste el inglés como a mi, ármese de valor.

Casimiro Notevi 27-04-2011 17:06:57

Cita:

Empezado por Ñuño Martínez (Mensaje 398135)
Un poco de off-topic y de SPAM: Hace un tiempo escribí un artículo titulado El Valor de un Céntimo, que está relacionado con el tema del manejo de valores fraccionarios en computadoras digitales. Seguro que te clarifica las cosas.

En la última gestión que tuve la suerte de decidir en todos los aspectos, también use algo similar, todos los importes se guardaban en un formato y se presentaban al usuario según la divisa que estuviese usando.

darkerbyte 16-05-2011 16:24:16

Es mas de lo que pensé
 
Ok, Ahora tengo mucha tarea :S

Gracias por los valiosos consejos. Y le entraré con valor al inglés aunque no me gusta.

darkerbyte 16-05-2011 16:36:21

Inteligente truco
 
Cita:

Empezado por Ñuño Martínez (Mensaje 398135)
Un poco de off-topic y de SPAM: Hace un tiempo escribí un artículo titulado El Valor de un Céntimo, que está relacionado con el tema del manejo de valores fraccionarios en computadoras digitales. Seguro que te clarifica las cosas.

Ya lei el artículo, me pareció muy interesante la manera que propusiste sobre manejar los precios en formato de enteros

Cita:

En aquel programa, en lugar de almacenar el valor real de los productos guardábamos un valor ficticio y lo guardábamos como un número entero. Para obtener el valor real se dividía este número ficticio por un factor que dependía de la divisa; así para los euros dividíamos por 100.000, mientras que para obtenerlo en pesetas dividíamos por 601. Con este método nos quitamos dos problemas: la conversión entre divisas (importante, porque se hizo para un hotel, y además coincidió con el paso de la peseta al euro) y teníamos una precisión de hasta la milésima de céntimo de euro, más de lo necesario
Me podrías explicar un poco mas sobre este método que inventaste. ¿Cómo determinas el valor ficticio? ¿Podrias mostrarnos como lo usabas, por ejemplo para obtener dólares y euros?

Muchas gracias, excelente aporte

Ñuño Martínez 16-05-2011 17:59:58

Cita:

Empezado por darkerbyte (Mensaje 400156)
Me podrías explicar un poco mas sobre este método que inventaste. ¿Cómo determinas el valor ficticio? ¿Podrias mostrarnos como lo usabas, por ejemplo para obtener dólares y euros?

Por supuesto que puedo explicarlo mejor. Eso sí, no lo inventé yo ya que no es sino un cambio de unidad, algo parecido a (por ejemplo) pasar de kilómetros a millas.

Verás, primero tenemos una tabla denominada divisa, más o menos así:
Código SQL [-]
CREATE TABLE divisas
  nombre VARCHAR (32),
  simbolo VARCHAR (4),
  valor INTEGER,
  num_decimales INTEGER
Ahora imaginemos que nuestra moneda principal serán los euros. En principio podríamos ponerle el valor "1", pero tiene céntimos así que sería más lógico que fuera "100". Sin embargo hay divisas cuyo valor es inferior al céntimo de euro (como la antigua peseta, por ejemplo) así que le damos el valor "10.000". Pues damos de alta a la divisa:
Código SQL [-]
INSERT INTO divisa (nombre, simbolo, valor, num_decimales)
VALUES ('euro', '€', 10000, 2);
Ahora metemos dólares. Primero, tenemos que saber cuántos euros es un dólar, que según Google, cuando escribo esto es 1U$ = 0'707063565€. Multiplicamos por el valor del euro, esto es, por 10.000, y nos sale 7.071:
Código SQL [-]
INSERT INTO divisa (nombre, simbolo, valor, num_decimales)
VALUES ('dólar estadounidense', 'U$', 7071, 2);
Ahora vamos a por los productos:
Código SQL [-]
CREATE TABLE productos
  nombre VARCHAR (256)
  precio INTEGER;
Como ves, el precio es un entero. Para introducir el precio, pues sólo tenemos que multiplicarlo por el valor de la divisa en la que hayamos introducido el precio:
Código SQL [-]
%VALOR_DIVISA% = SELECT valor FROM divisas WHERE nombre=%DIVISA_SELECCIONADA%;
INSERT INTO productos (nombre, precio)
VALUES ('chocolatina', (1.20 * %VALOR_DIVISA%));
Y ya está. Ahora todos los cálculos los hacemos con enteros, pero cuando mostremos el precio tenemos que dividir por el valor de la divisa y nos dará el precio.

Por ejemplo, nuestra chocolatina:
Código:

En euros:
  12.000 / 10.000 = 1'20€
En dólares:
  12.000 / 7.071 = 1'68U$

Que conste que esto no soluciona completamente el tema del redondeo. Es más, según Google la chocolatina cuesta 1'70U$: dos centavos más.

Espero que haya quedado más claro.

darkerbyte 17-05-2011 17:51:04

Podria marcar una tendencia
 
Hola de nuevo Ñuño.

Gracias por tomarte el tiempo y la molestia para esta excelente explicación. Ahora estoy evaluando de que manera puedo implementarlo al proyecto, aunque ahora es un poco complicado ya que habria que cambiar la estructura de la BD. Pero creo que el cambio bien valdría la pena.

Espero que pronto algunos mas de nuestros expertos comenten y enriquescan tu aporte, yo creo que incluso podría incluirse a la sección de Trucos.

Delphius 17-05-2011 20:41:18

Hola,
Lo que comenta Ñuño es más o menos similar a lo que comenté en una ocasión en un hilo en DA. La idea es que todo se opere a nivel de enteros y sólo a efectos de presentación se da su equivalente en formato real, con decimales.

Recomendaría la lectura de todo el hilo, ofrece un interesante planteo de lo que es el mundillo de los números flotantes.

Saludos,

Casimiro Notevi 17-05-2011 22:30:30

Estas cosas son las que se aprenden con la experiencia, después de haber sufrido algún problema con clientes que no le cuadran las cuentas y tienes que volverte loco buscando una buena solución .

Ñuño Martínez 18-05-2011 10:56:35

Cita:

Empezado por Delphius (Mensaje 400342)
Hola,
Lo que comenta Ñuño es más o menos similar a lo que comenté en una ocasión en un hilo en DA. La idea es que todo se opere a nivel de enteros y sólo a efectos de presentación se da su equivalente en formato real, con decimales.

Recomendaría la lectura de todo el hilo, ofrece un interesante planteo de lo que es el mundillo de los números flotantes.

Saludos,

Pues sí, muy interesante el hilo y las explicaciones.

Por cierto, que algunas bases de datos (como dBase) y lenguajes de programación (como COBOL) no usan punto flotante, sino algún tipo punto fijo (sea este en base decimal o binaria, o codificándolo diréctamente a ASCII), lo cual es más o menos lo que hemos explicado aquí y en el hilo que has enlazado. Incluso algunos microporcesadores, como el mítico Z-80, permitían operar directamente con BCD u otras notaciones de punto fijo. Creo que PowerPC permite usar tanto punto flotante como el fijo.


La franja horaria es GMT +2. Ahora son las 17:57:30.

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