Sé que Python //
redondea hacia el infinito negativo y en C++ /
se trunca, redondeando hacia 0.
Y esto es lo que sé hasta ahora:
|remainder| -12 / 10 = -1, - 2 // C++ -12 // 10 = -2, + 8 # Python 12 / -10 = -1, 2 // C++ 12 // -10 = -2, - 8 # Python 12 / 10 = 1, 2 // Both 12 // 10 = 1, 2 -12 / -10 = 1, - 2 // Both = 2, + 8 C++: 1. m%(-n) == m%n 2. -m%n == -(m%n) 3. (m/n)*n + m%n == m Python: 1. m%(-n) == -8 == -(-m%n) 2. (m//n)*n + m%n == m
Pero, ¿por qué Python //
elige redondear hacia el infinito negativo? No encontré ningún recurso que explique eso, pero solo encontré y escuché a la gente decir vagamente: "por razones matemáticas" .
Por ejemplo, en ¿Por qué -1/2 se evalúa como 0 en C++, pero como -1 en Python? :
Las personas que manejan estas cosas en abstracto tienden a sentir que redondear hacia el infinito negativo tiene más sentido ( eso significa que es compatible con la función de módulo como se define en matemáticas, en lugar de que % tenga un significado un tanto divertido ).
Pero no veo que C++ /
no sea compatible con la función de módulo. En C++, (m/n)*n + m%n == m
también se aplica.
Entonces, ¿cuál es la razón (matemática) por la que Python elige el redondeo hacia el infinito negativo?
Consulte también la publicación de blog anterior de Guido van Rossum sobre el tema .
Aunque no puedo proporcionar una definición formal de por qué/cómo se eligieron los modos de redondeo, la cita sobre la compatibilidad con el operador %
, que ha incluido, tiene sentido si considera que %
no es exactamente lo mismo en C++ y Python.
En C++, es el operador de resto , mientras que en Python es el operador de módulo y, cuando los dos operandos tienen signos diferentes , no son necesariamente lo mismo. Hay algunas buenas explicaciones de la diferencia entre estos operadores en las respuestas a: ¿Cuál es la diferencia entre "mod" y "resto"?
Ahora, teniendo en cuenta esta diferencia, los modos de redondeo (truncamiento) para la división de enteros deben ser como en los dos idiomas, para garantizar que la relación que citó, (m/n)*n + m%n == m
, permanezca válido.
Aquí hay dos programas cortos que demuestran esto en acción (perdone mi código de Python algo ingenuo, soy un principiante en ese idioma):
C++:
#include <iostream> int main() { int dividend, divisor, quotient, remainder, check; std::cout << "Enter Dividend: "; // -27 std::cin >> dividend; std::cout << "Enter Divisor: "; // 4 std::cin >> divisor; quotient = dividend / divisor; std::cout << "Quotient = " << quotient << std::endl; // -6 remainder = dividend % divisor; std::cout << "Remainder = " << remainder << std::endl; // -3 check = quotient * divisor + remainder; std::cout << "Check = " << check << std::endl; // -27 return 0; }
Pitón:
print("Enter Dividend: ") # -27 dividend = int(input()) print("Enter Divisor: ") # 4 divisor = int(input()) quotient = dividend // divisor; print("Quotient = " + str(quotient)) # -7 modulus = dividend % divisor; print("Modulus = " + str(modulus)) # 1 check = quotient * divisor + modulus; # -27 print("Check = " + str(check))
Tenga en cuenta que, para las entradas dadas de diferentes signos (-27 y 4), tanto el cociente como el resto/módulo son diferentes entre los idiomas, pero también que el valor de check
restaurado es correcto en ambos casos .
Tanto la aritmética de números enteros como la de números reales definen sus operadores de división para que las dos equivalencias siguientes se cumplan para todos los valores de n y d.
(n+d)/d = (n/d)+1 (-n)/d = -(n/d)
Desafortunadamente, la aritmética de enteros no se puede definir de tal manera que ambos se cumplan. Para muchos propósitos, la primera equivalencia es más útil que la segunda, pero en la mayoría de las situaciones donde el código estaría dividiendo dos valores, se aplicaría uno de los siguientes:
Ambos valores son positivos, en cuyo caso la segunda equivalencia es irrelevante.
El dividendo es un múltiplo entero preciso del divisor, en cuyo caso ambas equivalencias pueden cumplirse simultáneamente.
Históricamente, la forma más fácil de manejar la división que involucraba números negativos era observar si exactamente un operando era negativo, descartar los signos, realizar la división y luego hacer que el resultado fuera negativo si exactamente un operando era negativo. Esto se comportaría como se requiere en ambas situaciones comunes y sería más económico que usar un enfoque que mantuviera la primera equivalencia en todos los casos, en lugar de solo cuando el divisor fuera un múltiplo exacto del dividendo.
No se debe considerar que Python usa una semántica inferior, sino que debe decidir que la semántica que generalmente sería superior en los casos importantes valdría la pena hacer la división un poco más lenta , incluso en los casos en los que la semántica precisa no importaría .
"por razones matemáticas"
Considere el problema (bastante común en los videojuegos) donde tiene una coordenada X que puede ser negativa y desea traducirla a una coordenada de mosaico (supongamos mosaicos de 16x16) y un desplazamiento dentro del mosaico
El %
de Python te da el desplazamiento directamente, y /
te da el mosaico directamente:
>>> -35 // 16 # If we move 35 pixels left of the origin... -3 >>> -35 % 16 # then we are 13 pixels from the left edge of a tile in row -3. 13
(Y divmod
te da ambos a la vez).
Python's a // b = piso (a/b) en notación matemática estándar (ASCII). (En Alemania, la notación de Gauss [x] es común para el piso (x).) La función del piso es muy popular (a menudo se usa ⇔ útil; google para ver millones de ejemplos). En primer lugar, probablemente porque es simple y natural: "entero más grande ≤ x". Como consecuencia, disfruta de muchas buenas propiedades matemáticas, como:
Cualquier definición de la función "redondear hacia cero" que se me ocurra sería mucho más "artificial" e involucraría si-entonces (posiblemente oculto en el valor absoluto |.| o similar). No conozco ningún libro de matemáticas que presente una función de "redondeo hacia cero". Esa ya es una razón suficientemente buena para adoptar esta convención en lugar de la otra.
No me extenderé mucho en el argumento de "compatibilidad con la operación de módulo" detallado en otras respuestas, pero debe mencionarse ya que, por supuesto, también es un argumento válido y está vinculado a la fórmula de "traducción" anterior. Por ejemplo, en funciones trigonométricas, cuando necesitas el ángulo módulo 2 π, definitivamente es esta división la que necesitarás.
Aquí escribo div
para el operador de división de enteros y mod
para el operador de resto.
div
y mod
deben definirse de tal manera que, para a
, b
enteros con b
distinto de cero, tenemos
a == (a div b)*b + (a mod b)
A menudo, desea que los resultados de la mod
estén siempre entre 0 (inclusive) b
(exclusivo), independientemente del signo de a
. Por ejemplo, a
podría ser un índice en un búfer circular y b
podría ser su tamaño (positivo). Entonces, a mod b
podría usarse como índice en la matriz de memoria subyacente, incluso si a
es negativo. De hecho, usar a == -1
para referirse al último elemento del búfer es bastante popular.
Esta puede ser una de las razones por las que Python redondea los cocientes hacia el infinito negativo. Por lo tanto, el resto es cero o tiene el signo del divisor, independientemente del signo del dividendo. Aquí hay una gráfica basada en letras para un divisor fijo:
^ y = x mod 5 4 | * * * * * | * * * * * | * * * * * * |* * * * * * 0 +----*----*----*----*----*-> -5 0 5 10 15 x
En C/C++, las cosas se vuelven un poco más complicadas porque los enteros tienen un ancho limitado.
Supongamos que a == INT_MIN
, que en la representación en complemento a dos es una potencia negativa de dos, y b == 3
. Si redondeamos cocientes tales que a mod b > 0
, entonces (a div b)*b
tendría que ser menor que INT_MIN
, lo que constituiría un desbordamiento de enteros con signo. Los efectos estarían entonces definidos por la implementación. La máquina podría incluso interrumpir la ejecución, por ejemplo, al compilar con la opción -ftrapv
de GCC. Pero la justificación para concretar el comportamiento de las operaciones de división y resto de enteros era deshacerse de tales incertidumbres.
Por lo tanto, la única opción que quedaba para C/C++ era redondear los cocientes hacia cero. Así, el resto, si es distinto de cero, tiene el signo del dividendo.
El inconveniente es que la gráfica del divisor fijo parece menos regular:
^ y = x mod 5 4 | * * * | * * * | * * * * | * * * * 0 | * * * * * | * * | * * | * * -4 |* * +----+----+----+----+----+-> -5 0 5 10 15 x
En consecuencia, mod
buffer-size no maneja valores de índice negativos como nos gustaría. En cuanto a la programación, no me gusta esta decisión, aunque puedo entender la lógica para cumplir con a == (a div b)*b + (a mod b)
incluso en casos extremos.
Pero, ¿por qué Python
//
elige redondear hacia el infinito negativo?
Según python-history.blogspot.com, Guido van Rossum eligió tal comportamiento para //
porque
(...) hay una buena razón matemática. La operación de división de enteros (//) y su hermana, la operación de módulo (%), van juntas y satisfacen una buena relación matemática (todas las variables son números enteros):
a/b = q con resto r
tal que
b*q + r = a y 0 <= r < b
(suponiendo que a y b son >= 0).
Si desea que la relación se extienda para a negativa (manteniendo b positiva), tiene dos opciones: si trunca q hacia cero, r se volverá negativa, de modo que el invariante cambie a 0 <= abs(r) < de lo contrario, puede bajar q hacia el infinito negativo, y el invariante sigue siendo 0 <= r < b(...) En teoría matemática de números, los matemáticos siempre prefieren la última opción (...). Para Python, tomé la misma decisión porque hay algunas aplicaciones interesantes de la operación de módulo donde el signo de a no es interesante. Considere tomar una marca de tiempo POSIX (segundos desde el comienzo de 1970) y convertirla en la hora del día. Como hay 24*3600 = 86400 segundos en un día, este cálculo es simplemente t % 86400. Pero si tuviéramos que expresar tiempos antes de 1970 usando números negativos, la regla de "truncar hacia cero" daría un resultado sin sentido. Usando la regla del piso todo sale bien. Otras aplicaciones en las que he pensado son los cálculos de posiciones de píxeles en gráficos por computadora. Estoy seguro de que hay más.
Entonces, resumiendo //
la elección del comportamiento se debe a mantenerlo consistente con el %
de comportamiento, este último fue seleccionado debido a su utilidad para trabajar con marcas de tiempo y píxeles negativos (antes del comienzo de 1970).
La razón matemática por la que Python elige redondear la división de enteros hacia el infinito negativo es que es la opción matemáticamente más consistente. En Python, cuando divides dos enteros, el resultado siempre será un número de punto flotante. Este número se redondeará al entero más cercano, con números positivos redondeando hacia arriba y números negativos redondeando hacia abajo. Este comportamiento de redondeo constante es lo que conduce al comportamiento de redondeo hacia el infinito negativo.
La razón matemática detrás del redondeo de la división de enteros de Python hacia el infinito negativo es que da resultados más consistentes que el redondeo hacia el infinito positivo. Por ejemplo, considere las siguientes dos expresiones:
3 / 4
-3 / 4
La primera expresión dará como resultado el valor 0,75, mientras que la segunda expresión dará como resultado el valor -0,75. Esto se debe a que la primera expresión se redondea hacia el infinito positivo, mientras que la segunda expresión se redondea hacia el infinito negativo.