Al pasar por la operación Modulo (la avenida que ingresé mientras exploraba la diferencia entre rem
ymod
) me encontré con:
En matemáticas, el resultado de la operación de módulo es el resto de la división euclidiana. Sin embargo, otras convenciones son posibles. Las computadoras y las calculadoras tienen varias formas de almacenar y representar números; por lo tanto, su definición de la operación del módulo depende del lenguaje de programación y / o del hardware subyacente.
Preguntas:
- Al pasar por la división euclidiana , descubrí que el resto de esta operación siempre es positivo (o 0). ¿Qué limitación del hardware informático subyacente obliga a los diseñadores de lenguajes de programación a diferenciarse de las matemáticas?
- Cada lenguaje de programación tiene una regla predefinida o indefinida según la cual el resultado de la operación de módulo obtiene su signo. ¿Qué razón se adopta al hacer estas reglas? Y si el hardware subyacente es la preocupación, ¿no deberían cambiar las reglas de acuerdo con eso, independientemente del lenguaje de programación?
programming-languages
language-design
math
arithmetic
design-decisions
Dedos sangrantes
fuente
fuente
(-3)/2 == -1
. Esta definición puede ser útil. Cuando desea%
ser coherente con el cumplimiento de esta divisiónx == (x/y)*y + x % y
, termina con la definición de%
utilizado en C #.Respuestas:
El hardware de todas las computadoras modernas es lo suficientemente potente como para implementar operaciones mod de cualquier signo sin impacto en el rendimiento (o trivial). Esta no es la razón.
La expectativa común de la mayoría de los lenguajes de computadora es que (a div b) * b + (a mod b) = a. En otras palabras, div y mod considerados juntos dividen un número en partes que se pueden volver a unir de manera confiable. Este requisito es explícito en el estándar C ++. El concepto está estrechamente relacionado con la indexación de matrices multidimensionales. Lo he usado a menudo.
De esto se puede ver que div y mod preservarán el signo de a si b es positivo (como suele serlo).
Algunos lenguajes proporcionan una función 'rem ()' que está relacionada con mod y tiene alguna otra justificación matemática. Nunca he necesitado usar esto. Ver por ejemplo frem () en Gnu C. [editado]
fuente
rem(a,b)
es más probablemod(a,b)
que sea positivo omod(a,b) + b
no.(a div b) * b + (a mod b) = a
- Esto, mucho. De hecho, al contrario de cómo Wikipedia describe extenderlo a números negativos en la división euclidiana (especialmente "El resto es el único de los cuatro números que nunca puede ser negativo") me confunde porque siempre me enseñaron que el resto puede ser negativo en cada clase de matemáticas a ese nivel.Para la programación que normalmente quieres
X == (X/n)*n + X%n
; por lo tanto, cómo se define el módulo depende de cómo se definió la división entera.Con esto en mente, realmente se pregunta " ¿Qué lógica se usa cuando los diseñadores de lenguaje de programación deciden cómo funciona la división de enteros? "
En realidad, hay alrededor de 7 opciones:
Ahora considera
-( (-X) / n) == X/n
. Me gustaría que esto sea cierto, ya que cualquier otra cosa parece inconsistente (es cierto para el punto flotante) e ilógico (una causa probable de errores y también una optimización potencialmente perdida). Esto hace que las 2 primeras opciones para la división de enteros (redondeando hacia el infinito) sean indeseables.Todas las opciones de "redondear a la más cercana" son una molestia para la programación, especialmente cuando haces algo como mapas de bits (por ejemplo
offset = index / 8; bitNumber = index%8;
).Eso deja el redondeo hacia cero como la opción "potencialmente más sana", lo que implica que el módulo devuelve un valor con el mismo signo que el numerador (o cero).
Nota: También notará que la mayoría de las CPU (todas las CPU que conozco) realizan la división de enteros de la misma manera "redonda a cero". Es probable que esto sea por las mismas razones.
fuente
(a+b*c)/b == a % b
ya >> n == a / 2 ** n
, para lo cual, la división de pisos tiene un comportamiento sensato.1 >> -2 == a / 2 ** (-2)
).(a + b * c) % b == a % b
, es decir, el%
operador es divisor periódico en el dividendo, que a menudo es importante. Por ejemplo, con la división de pisos,day_count % 7
le da el día de la semana, pero con la división truncada, esto se rompe para las fechas anteriores a la época.Primero, repetiré que un módulo b debería ser igual a a - b * (a div b), y si un lenguaje no proporciona eso, estás en un horrible lío matemático. Esa expresión a - b * (a div b) es en realidad cuántas implementaciones calculan un módulo b.
Hay algunas razones posibles. El primero es que desea la velocidad máxima, por lo que un div b se define como lo que proporcionará el procesador utilizado. Si su procesador tiene una instrucción "div", entonces un div b es lo que hace esa instrucción div (siempre y cuando sea algo no totalmente loco).
El segundo es que quieres un comportamiento matemático específico. Primero supongamos que b> 0. Es bastante razonable que desee que el resultado de un div b se redondee a cero. Entonces 4 div 5 = 0, 9 div 5 = 1, -4 div 5 = -0 = 0, -9 div 5 = -1. Esto le da (-a) div b = - (a div b) y (-a) módulo b = - (a módulo b).
Esto es bastante razonable pero no perfecto; por ejemplo (a + b) div b = (a div b) + 1 no se cumple, digamos si a = -1. Con un b> 0 fijo, generalmente hay (b) valores posibles para a de modo que un div b da el mismo resultado, excepto que hay 2b - 1 valores a de -b + 1 a b-1 donde a div b es igual a 0 También significa que un módulo b será negativo si a es negativo. Queremos que un módulo b sea siempre un número en el rango de 0 a b-1.
Por otro lado, también es bastante razonable solicitar que a medida que avanza por los valores sucesivos de a, un módulo b debe pasar por los valores de 0 a b-1 y luego comenzar de nuevo con 0. Y para solicitar que (a + b) div b sea (a div b) + 1. Para lograr eso, desea que el resultado de un div b se redondee hacia -infinito, entonces -1 div b = -1. De nuevo, hay desventajas. (-a) div b = - (a div b) no se cumple. Dividir repetidamente por dos o por cualquier número b> 1 eventualmente no le dará un resultado de 0.
Dado que hay conflictos, los idiomas tendrán que decidir qué conjunto de ventajas es más importante para ellos y decidir en consecuencia.
Para b negativo, la mayoría de las personas no pueden entender qué deberían ser un div b y un módulo b en primer lugar, por lo que una manera simple es definir que a div b = (-a) div (-b) y a módulo b = (-a) módulo (-b) si b <0, o lo que sea el resultado natural de usar el código para b positivo.
fuente