¿Necesito más decimales? 2 de 2

¡Saludos visitante! si deseas comentar o hacer una pregunta sobre este post por favor dirígete a la nueva dirección en http://variabletecnica.com.ve. La página que estás leyendo dejará de estar disponible el 15/11/2015. Gracias, y disculpa las molestias 🙂 Estamos acostumbrados a hacer sumas y multiplicaciones sin prestar mucha atención al redondeo de los decimales. La…


¡Saludos visitante! si deseas comentar o hacer una pregunta sobre este post por favor dirígete a la nueva dirección en http://variabletecnica.com.ve. La página que estás leyendo dejará de estar disponible el 15/11/2015. Gracias, y disculpa las molestias 🙂

Estamos acostumbrados a hacer sumas y multiplicaciones sin prestar mucha atención al redondeo de los decimales. La verdad es que al usuario común no le importa mucho: vamos al supermercado y seleccionamos algunos tomates, normalmente redondeamos el monto «a ojo» y pensamos «son más de 2Kg, debe ser unos 50Bs (Bs -> VEB)»; y no nos importa mucho si el monto final lo «redondean» de nuevo al 1Bs más cercano (ya nadie quiere el bolsillo lleno de moneditas de 5 céntimos).

¿Alguna vez has revisado con cuidado el cálculo de impuesto en las facturas fiscales? Me ocurrió un caso curioso en una pizzería cerca de mi oficina; para entonces mi pizza favorita costaba 40.00Bs. Un día me tocó comprar almuerzo para un amigo de la oficina, así que pedí 2 pizzas, la sorpresa es que dos pizzas no cuentan 80.00Bs ¡sino 79.99Bs!

Veamos que pasó: en esa pizzería, y supongo que en muchos establecimientos, siempre que sea posible fijan los precios de manera que sean cifras exactas: «venderemos la pizza a 40Bs», pero este monto ya debe incluir el impuesto (12% en Venezuela), así que en su sistema de facturación deben cargar el precio sin el impuesto correspondiente:

$latex Neto = 40.00 $

$latex Impuesto = 12.00 $

$latex Base = \frac{Neto}{1 + \frac{Impuesto}{100}} $

$latex Base = \frac{40.00}{1 + \frac{12.00}{100}} $

$latex Base = 35.7143 \approx 35.71 $

Entonces, ya se tiene el monto base 35.71 con 2 decimales. El programa de facturación no sabe nada de la política de «vender en montos exactos», por lo que al cargar una factura para un solo producto (pizza) simplemente toma el monto base, calcula el monto del impuesto y luego el monto neto:

$latex Base = 35.71 $

$latex Impuesto = 12.00 $

$latex MonImp = Base \times \frac{Impuesto}{100} $

$latex MonImp = 35.71 \times \frac{12.00}{100} $

$latex MonImp = 4.2852 \approx 4.29 $

$latex Neto = Base + MonImp $

$latex Neto = 40.00 $

Todo bien hasta ahora.

Pero… ¿Porqué 2*40.00Bs = 79.99Bs?

Veamos que pasa cuando son 2 artículos; digamos que los desarrolladores del sistema de facturación decidieron que se debía calcular los impuestos «después» de multiplicar la base por la cantidad:

$latex Base = 35.71 \times 2 = 71.42 $

$latex Impuesto = 12.00 $

$latex MonImp = Base \times \frac{Impuesto}{100} $

$latex MonImp = 71.42 \times \frac{12.00}{100} $

$latex MonImp = 8.5704 \approx 8.57 $

$latex Neto = Base \times MonImp $

$latex Neto = 79.99 $

La diferencia aquí es que el monto del impuesto se redondeó hacia arriba en el primer caso y hacia abajo en el segundo. Una posible solución sería siempre calcular el monto de impuesto y monto neto para cada unidad de producto, redondear y luego multiplicarla por el número de unidades del mismo. Con esto, los cálculos que originalmente se hicieron para una unidad se mantendrían para varias unidades.

Claro que si estamos hablando de facturas debes tomar en cuenta que las leyes de tu país/localidad pueden tener un criterio diferente que regule el cálculo del impuesto (he oído decir que Venezuela está entre los países con leyes más complicadas en materia de comercio).

Si las facturas deben ser emitidas por una impresora fiscal entonces no hay muchas opciones: a las impresoras con las que he trabajado solo se les indica el monto base, la cantidad de unidades y el porcentaje de impuesto, y ellas hacen los cálculos; así que debes hacer los cálculos exactamente igual a como lo hace la impresora, de manera que los montos registrados en el sistema de facturación coincidan con los de la impresora.

Más Acerca del Cálculo de Porcentajes

Con frecuencia tendemos a dar por hecho afirmaciones que son obvias… o que al menos parecen obvias hasta que empezamos a programar. Supongamos que en nuestro programa hay que dividir cierto monto Z en n partes iguales; muchos podrían sentirse tentados a decir que el tamaño de cada parte seria igual a P=Z/n, y asunto resuelto.

Pero, ¿y si el monto no es exactamente divisible entre n?. Digamos que vas a asignar parte de un presupuesto a 3 proyectos de tu empresa, y como monto de ejemplo escojamos 1.430,31 Bs, que es exactamente divisible en tres partes iguales, con dos decimales.

Proyecto Monto Asignado Porcentaje Asignado
Cafetera nueva  476,77  33,33%
Publicidad  476,77  33,33%
Fusión en frío  476,77  33,33%
Total  1.430,31 99,99%

Vemos la suma de los tres montos coincide con el monto original ¡pero los porcentajes no suman 100%! claramente el problema es que 100 no es divisible entre 3.

Bien, el problema es bastante obvio pero no la solución; incluso «corregir» los valores puede traernos más problemas todavía: ¿Qué tal si mejor repartimos el 100% en tres partes «casi» iguales y después calculamos los montos correspondientes a cada porcentaje? con esto aseguramos que tanto la suma de los montos como la suma de los porcentajes sea exacta (sic). Veamos:

Proyecto Porcentaje Asignado Monto Asignado
Cafetera nueva 33,33% 476,72
Publicidad 33,33% 476,72
Fusión en frío 33,34% 476,87
Total 100,00% 1.430,31

¡Excelente! después de haber realizado dos pruebas «con un único caso» podríamos dar por terminado un «completo» análisis de nuestro problema y empezar a programar ese método que tantos problemas nos ha dado por los decimales. Ejem… que tal si antes de dar por cerrado el asunto probamos nuestra «solución» con otras dos cifra para estar seguros, digamos 17,11Bs y 17,12Bs:

Proyecto Porcentaje Asignado Monto_1: 17,11 Monto_2: 17,12
Cafetera nueva 33,33% 5,70 5,71
Publicidad 33,33% 5,70 5,71
Fusión en frío 33,34% 5,70 5,71
Total

100,00%

17,10 17,13

Si, ya se como te sientes… es un poco frustrante cuando crees que resolviste el problema e intentas mostrarselo a alguien solo para econtrarte con que aun no está resuelto.

Escogí ese par de cifras luego de experimentar un poco, porque al ser valores relativamente pequeños la magnitud relativa del error de redondeo es más notoria. Incluso si completamos con otros valores de prueba veremos que 17,09 y 17,10 dan los mismos valores que con 17,11; mientras que con 17,13 y 17,14 dan los mismos valores que con 17,12; claro, siempre redondeando los valores a dos cifras decimales.

Yo he encontrado errores hasta dos años después de haber diseñado e implantado una «solución definitiva», solo es cuestión de validar lo suficiente el sistema (en algunos casos en tiempo de operación) o, si realmente cuentas con el tiempo, estudiar con cuidado los posibles casos que tu programa pudiese enfrentar.

Para Terminar…

La intención de este post es mostrar como algunas operaciones matemáticas obvias, resultan en errores difíciles de notar debido a que estamos acostumbrados a hacerlas de forma automática.

En las clases de Matemática desde secundaria nos enseñan a trabajar con operaciones algebraicas, obteniendo siempre resultados «exactos» en forma de fracciones irreducibles, logaritmos, radicales y exponentes. Pero en la programación normalmente nos encontramos con situaciones más bien parecidas a la física de secundaria, donde los resultados generalmente tienen cierto grado de exactitud (que no debe confundirse con la Presición del resultado) que es permisible siempre y cuando estés al tanto de la magnitud del posible error en tus cálculos: a un cliente de un supermercado no le importará mucho que te equivoques por 0,01Bs en el monto total a pagar, pero es posible el organismo tributario de tu localidad (SENIAT) no acepte un error como ese en el cálculo del impuesto.

Al programar debemos establecer un compromiso entre la exactitud de los cálculos (más decimales, diferente enfoque de cálculo…), el tiempo disponible para programar (investigación, diseño e implementación de un nuevo algoritmo) y el rendimiento en tiempo de ejecución (cálculos complejos, métodos iterativos…), para que nuestro programa ejecute su trabajo de forma «natural» desde el punto de vista del usuario.

Si no necesitas que tus cuentas «cuadren» entonces puedes programar como siempre, pero si los valores deben ser exactos tendrás que prestar mucha atención en los cálculos y diseñar con cuidado tus algoritmos para disminuir la posibilidad de errores.

Por último una recomendación: no siempre los problemas «simples» tienen soluciones simples, piensa con cuidado tu solución, son pocas ocasiones en que realmente necesitas más decimales.

Cita del día

42 (Answer to the Ultimate Question of Life, the Universe, and Everything)
Douglas Addams; Life, the Universe and Everything.

,

Soluciones claras y simples



Ing. Industrial, dedicado a la programación en ASP.NET+VB, SQL y Javascript+AJAX; y un poco de Android, Kotling, y Unity 🙂
Valencia, Venezuela



¿QUIERES APOYARME?

¿Te ha sido de ayuda alguno de mis artículos? Generar contenido técnico requiere de tiempo y esfuerzo. Con tu colaboración me puedes ayudar a mantener mi blog activo y actualizado. Si quieres y puedes apoyarme has clic aquí:

https://paypal.me/roimergarcia


ENTRADAS RECIENTES