¿Qué razón se usa cuando los diseñadores de lenguaje de programación deciden qué signo toma el resultado de la operación del módulo?


9

Al pasar por la operación Modulo (la avenida que ingresé mientras exploraba la diferencia entre remymod ) 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?

1
En mi código casi siempre necesito módulo, no el resto. No tengo idea de por qué el resto es tan popular.
CodesInChaos

8
Relacionado ¿Cuál es la diferencia? Remainder vs Modulus - Blog de Eric Lippert (por uno de los diseñadores de C #, pero creo que se unió al equipo después de tomar esta decisión)
CodesInChaos

1
Si continúa leyendo el artículo de Wikipedia (más allá de la parte que citó), explica lo que citó bastante bien. ¿Sobre qué explicación estás confundido?
Robert Harvey

1
Una pregunta relacionada es cuál de estas operaciones se asigna directamente a las instrucciones de la CPU. En c se define su implementación, que se ajusta a la filosofía c de mapear directamente al hardware en tantas plataformas como sea posible. Por lo tanto, no especifica cosas que pueden diferir entre las CPU.
CodesInChaos

55
La programación de @BleedingFingers a menudo usa una división entera que va hacia cero, por ejemplo (-3)/2 == -1. Esta definición puede ser útil. Cuando desea %ser coherente con el cumplimiento de esta división x == (x/y)*y + x % y, termina con la definición de %utilizado en C #.
CodesInChaos

Respuestas:


6

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]


Creo que rem(a,b)es más probable mod(a,b)que sea positivo o mod(a,b) + bno.
user40989

3
(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.
Izkata

@ user40989: dije que nunca lo había usado. Ver editar!
david.pfx

4

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:

  • redondear a infinito negativo
  • redondear a infinito positivo
  • redondear a cero
  • varias versiones de "redondear al más cercano" (con diferencias en cómo se redondea algo como 0.5)

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.


Pero la división truncada también tiene sus propias inconsistencias: se rompe (a+b*c)/b == a % by a >> n == a / 2 ** n, para lo cual, la división de pisos tiene un comportamiento sensato.
dan04

Tu primer ejemplo no tiene sentido. Su segundo ejemplo es un desastre para los programadores: para positivo a y positivo n es consistente, para negativo a y positivo n depende de cómo se defina el desplazamiento a la derecha (aritmético frente a lógico), y para negativo n está roto (por ejemplo 1 >> -2 == a / 2 ** (-2)).
Brendan

El primer ejemplo fue un error tipográfico: quise decir (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 % 7le da el día de la semana, pero con la división truncada, esto se rompe para las fechas anteriores a la época.
dan04

0

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.