truncar redondear imprimir exponente entero entera decimales python python-2.7 python-3.x floating-point rounding

redondear - errores de redondeo en la división de piso de Python



redondear decimales python (5)

@jotasi explicó la verdadera razón detrás de esto.

Sin embargo, si desea evitarlo, puede usar decimal módulo decimal que básicamente fue diseñado para representar números de punto flotante decimal exactamente en contraste con la representación de punto flotante binario.

Entonces en tu caso podrías hacer algo como:

>>> from decimal import * >>> Decimal(''8.0'')//Decimal(''0.4'') Decimal(''20'')

Referencia: https://docs.python.org/2/library/decimal.html

Sé que los errores de redondeo suceden en la aritmética de punto flotante, pero ¿alguien puede explicar la razón de esto?

>>> 8.0 / 0.4 # as expected 20.0 >>> floor(8.0 / 0.4) # int works too 20 >>> 8.0 // 0.4 # expecting 20.0 19.0

Esto sucede tanto en Python 2 como en 3 en x64.

Por lo que veo, esto es un error o una especificación muy tonta de // ya que no veo ningún motivo por el cual la última expresión deba evaluarse a 19.0 .

¿Por qué simplemente no se define a // b como floor(a / b) ?

EDITAR : 8.0 % 0.4 también evalúa a 0.3999999999999996 . Al menos esto es consecuente ya que entonces 8.0 // 0.4 * 0.4 + 8.0 % 0.4 evalúa a 8.0

EDITAR : Esto no es un duplicado de ¿Están rotas las matemáticas de coma flotante? ya que estoy preguntando por qué esta operación específica está sujeta a errores de redondeo (quizás evitables) y por qué a // b no se define como / igual a floor(a / b)


Como ya han notado usted y Khelwood, 0.4 no se puede representar exactamente como un flotador. ¿Por qué? Es dos quintos ( 4/10 == 2/5 ) que no tiene una representación de fracción binaria finita.

Prueba esto:

from fractions import Fraction Fraction(''8.0'') // Fraction(''0.4'') # or equivalently # Fraction(8, 1) // Fraction(2, 5) # or # Fraction(''8/1'') // Fraction(''2/5'') # 20

sin embargo

Fraction(''8'') // Fraction(0.4) # 19

Aquí, 0.4 se interpreta como un literal flotante (y por lo tanto un número binario de coma flotante) que requiere redondeo (binario) y solo luego se convierte al número racional Fraction(3602879701896397, 9007199254740992) , que es casi pero no exactamente Fraction(3602879701896397, 9007199254740992) . Entonces se ejecuta la división con piso, y porque

19 * Fraction(3602879701896397, 9007199254740992) < 8.0

y

20 * Fraction(3602879701896397, 9007199254740992) > 8.0

el resultado es 19, no 20.

Lo mismo pasa probablemente para

8.0 // 0.4

Es decir, parece que la división con piso se determina atómicamente (pero en los únicos valores flotantes aproximados de los literales float interpretados).

Entonces, ¿por qué?

floor(8.0 / 0.4)

dar el resultado "correcto"? Porque allí, dos errores de redondeo se anulan mutuamente. Primero 1) la división se realiza, produciendo algo ligeramente menor que 20.0, pero no representable como flotante. Se redondea al flotador más cercano, que resulta ser 20.0 . Solo entonces , se realiza la operación de floor , pero ahora actúa exactamente en 20.0 , sin cambiar el número.

1) Como señala Kyle Strand, que el resultado exacto se determina y luego redondeado no es lo que realmente ocurre bajo 2) -level (código C de CPython o incluso instrucciones de la CPU). Sin embargo, puede ser un modelo útil para determinar el 3) resultado esperado.

2) En el nivel más bajo 4) , sin embargo, esto podría no estar muy lejos. Algunos conjuntos de chips determinan los resultados de flotación calculando primero un resultado de punto flotante interno más preciso (pero aún no exacto, simplemente tiene algunos dígitos binarios) y luego redondeando a doble precisión IEEE.

3) "esperado" por la especificación de Python, no necesariamente por nuestra intuición.

4) Bueno, el nivel más bajo por encima de las puertas lógicas. No tenemos que considerar la mecánica cuántica que hace que los semiconductores sean posibles para comprender esto.


Después de verificar las fuentes semifuncionales del objeto float en cpython en github ( https://github.com/python/cpython/blob/966b24071af1b320a1c7646d33474eeae057c20f/Objects/floatobject.c ) uno puede entender lo que sucede aquí.

Para la división normal se llama a float_div (línea 560) que internamente convierte el float python en c- double s, hace la división y luego convierte el double resultante en un float python. Si simplemente haces eso con 8.0/0.4 en c obtienes:

#include "stdio.h" #include "math.h" int main(){ double vx = 8.0; double wx = 0.4; printf("%lf/n", floor(vx/wx)); printf("%d/n", (int)(floor(vx/wx))); } // gives: // 20.000000 // 20

Para la división de piso, sucede algo más. Internamente, se float_floor_div (línea 654), que luego llama a float_divmod , una función que se supone devuelve una tupla de float de python que contiene la división con piso, así como el mod / remainder, aunque este último simplemente se descarta. PyTuple_GET_ITEM(t, 0) . Estos valores se calculan de la siguiente manera (después de la conversión a c- double s):

  1. El resto se calcula usando double mod = fmod(numerator, denominator) .
  2. El numerador se reduce por mod para obtener un valor integral cuando haces la división.
  3. El resultado para la división con piso se calcula al calcular efectivamente el floor((numerator - mod) / denominator)
  4. Luego, se completa el control ya mencionado en la respuesta de @Kasramvd. Pero esto solo ajusta el resultado de (numerator - mod) / denominator al valor integral más cercano.

La razón por la cual esto da un resultado diferente es que fmod(8.0, 0.4) debido a la aritmética de punto flotante da 0.4 lugar de 0.0 . Por lo tanto, el resultado que se calcula es en realidad floor((8.0 - 0.4) / 0.4) = 19 y snapping (8.0 - 0.4) / 0.4) = 19 al valor integral más cercano no corrige el error introducido por el "incorrecto" resultado de fmod . Usted también puede chaquear eso en c:

#include "stdio.h" #include "math.h" int main(){ double vx = 8.0; double wx = 0.4; double mod = fmod(vx, wx); printf("%lf/n", mod); double div = (vx-mod)/wx; printf("%lf/n", div); } // gives: // 0.4 // 19.000000

Supongo que eligieron esta forma de calcular la división con piso para mantener la validez de (numerator//divisor)*divisor + fmod(numerator, divisor) = numerator (como se menciona en el enlace en la respuesta de @ 0x539), aunque esto ahora resulta en un comportamiento algo inesperado de floor(8.0/0.4) != 8.0//0.4 .


Eso es porque no hay 0.4 en python (representación finita de punto flotante) en realidad es un flotador como 0.4000000000000001 que hace que el piso de división sea 19.

>>> floor(8//0.4000000000000001) 19.0

Pero la división verdadera ( / ) devuelve una aproximación razonable del resultado de la división si los argumentos son flotantes o complejos. Y es por eso que el resultado de 8.0/0.4 es 20. De hecho, depende del tamaño de los argumentos (en C argumentos dobles). ( no redondeando al flotador más cercano )

Lea más sobre los pisos de división enteros de pitones por el propio Guido.

También para obtener información completa sobre los números flotantes, puede leer este artículo https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Para aquellos que tienen interés, la siguiente función es el float_div que hace la tarea de división verdadera para los números flotantes, en el código fuente de Cpython:

float_div(PyObject *v, PyObject *w) { double a,b; CONVERT_TO_DOUBLE(v, a); CONVERT_TO_DOUBLE(w, b); if (b == 0.0) { PyErr_SetString(PyExc_ZeroDivisionError, "float division by zero"); return NULL; } PyFPE_START_PROTECT("divide", return 0) a = a / b; PyFPE_END_PROTECT(a) return PyFloat_FromDouble(a); }

Que el resultado final sería calculado por la función PyFloat_FromDouble :

PyFloat_FromDouble(double fval) { PyFloatObject *op = free_list; if (op != NULL) { free_list = (PyFloatObject *) Py_TYPE(op); numfree--; } else { op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject)); if (!op) return PyErr_NoMemory(); } /* Inline PyObject_New */ (void)PyObject_INIT(op, &PyFloat_Type); op->ob_fval = fval; return (PyObject *) op; }


Ok, después de un poco de investigación, he encontrado este issue . Lo que parece estar sucediendo es que, como @khelwood sugirió que 0.4 evalúa internamente a 0.40000000000000002220 , que al dividir 8.0 cede algo ligeramente más pequeño que 20.0 . El operador / redondea al número de coma flotante más cercano, que es 20.0 , pero el operador // inmediatamente trunca el resultado, obteniendo 19.0 .

Esto debería ser más rápido y supongo que está "cerca del procesador", pero todavía no es lo que el usuario quiere / está esperando.