Operación de módulo con números negativos


196

En un programa en C, estaba probando las siguientes operaciones (solo para verificar el comportamiento)

 x = 5 % (-3);
 y = (-5) % (3);
 z = (-5) % (-3); 

printf("%d ,%d ,%d", x, y, z); 

me dio salida como (2, -2 , -2)en gcc. Esperaba un resultado positivo cada vez. ¿Puede un módulo ser negativo? ¿Alguien puede explicar este comportamiento?



1
posible duplicado del operador
sugavaneshb

Respuestas:


170

C99 requiere que cuando a/bsea ​​representable:

(a/b) * b + a%b será iguala

Esto tiene sentido, lógicamente. ¿Correcto?

Veamos a qué conduce esto:


El ejemplo A. 5/(-3)es-1

=> (-1) * (-3) + 5%(-3) =5

Esto solo puede suceder si 5%(-3)es 2.


El ejemplo B. (-5)/3es-1

=> (-1) * 3 + (-5)%3 =-5

Esto solo puede suceder si (-5)%3es-2


1
¿Debería el compilador ser lo suficientemente inteligente y detectar que un módulo sin signo y otro sin signo siempre es positivo? Actualmente (bueno, GCC 5.2) el compilador parece pensar que "%" devuelve un "int" en este caso, en lugar de "unsigned" incluso cuando ambos operandos son uint32_t o más grandes.
Frederick Nord

@FrederickNord ¿Tiene un ejemplo para mostrar ese comportamiento ?
chux - Restablece a Monica

11
Comprenda que lo que describe es la descripción habitual de int (a / b) (truncada) de mod. Pero también es posible que la regla sea floor (a / b) (Knuth). En el caso Knuth -5/3es -2y el mod se convierte en 1. En resumen: un módulo tiene un signo que sigue al signo de dividendo (truncado), el otro módulo tiene un signo que sigue al signo divisor (Knuth).
Isaac

1
Este es un caso de que el estándar C no es exactamente lo que quiero. Nunca he querido truncar a cero o números de módulo negativos, pero a menudo quiero lo contrario y necesito trabajar alrededor de C.
Joe

145

El %operador en C no es el operador de módulo sino el operador restante .

Los operadores de módulo y resto difieren con respecto a los valores negativos.

Con un operador restante, el signo del resultado es el mismo que el del dividendo, mientras que con un operador de módulo el signo del resultado es el mismo que el divisor.

C define la %operación para a % bcomo:

  a == (a / b * b) + a % b

con /la división entera con truncamiento hacia 0. Ese es el truncamiento que se hace hacia 0(y no hacia la inifinidad negativa) que define el %como un operador restante en lugar de un operador de módulo.


8
El resto es el resultado de la operación del módulo por definición. No debería haber tal cosa como operador de resto porque no existe tal cosa como operación de resto, se llama módulo.
gronostaj

41
@gronostaj no en CS. Mire los lenguajes de nivel superior como Haskell o Scheme que definen dos operadores diferentes ( remaindery moduloen Scheme remy moden Haskell). Las especificaciones de estos operadores difieren en estos lenguajes en cómo se realiza la división: truncamiento hacia 0 o hacia infinito negativo. Por cierto estándar de C nunca llama a la %del operador de módulo , sólo que nombre que el operador% .
ouah

2
No debe confundirse con la remainder función en C, que implementa el resto IEEE con la semántica de redondeo hacia el más cercano en la división
Eric

68

Basado en la especificación C99: a == (a / b) * b + a % b

¡Podemos escribir una función para calcular (a % b) == a - (a / b) * b!

int remainder(int a, int b)
{
    return a - (a / b) * b;
}

Para la operación de módulo, podemos tener la siguiente función (suponiendo b > 0)

int mod(int a, int b)
{
    int r = a % b;
    return r < 0 ? r + b : r;
}

Mi conclusión es que a % ben C es una operación restante y NO una operación de módulo.


3
Esto no da resultados positivos cuando bes negativo (y de hecho para ry bambos negativos que da resultados menos -b). Para garantizar resultados positivos para todas las entradas que pueda usar r + abs(b)o para que coincidan con bel signo, puede cambiar la condición a r*b < 0.
Martin Ender

@MartinEnder r + abs(b)es UB cuando b == INT_MIN.
chux

60

No creo que haya necesidad de verificar si el número es negativo.

Una función simple para encontrar el módulo positivo sería esta:

Editar: Suponiendo N > 0yN + N - 1 <= INT_MAX

int modulo(int x,int N){
    return (x % N + N) %N;
}

Esto funcionará tanto para valores positivos como negativos de x.

PS original: también según lo indicado por @chux, si su x y N pueden alcanzar algo como INT_MAX-1 e INT_MAX respectivamente, simplemente reemplace intcon long long int.

Y si también están cruzando límites de largo largo (es decir, cerca de LLONG_MAX), entonces manejará los casos positivos y negativos por separado como se describe en otras respuestas aquí.


1
Tenga en cuenta que cuando N < 0, el resultado puede ser negativo como en modulo(7, -3) --> -2. También x % N + Npuede desbordar las intmatemáticas, que es un comportamiento indefinido. por ejemplo, modulo(INT_MAX - 1,INT_MAX)podría resultar en -3.
chux

Sí, en ese caso, simplemente puede usar long long into manejar el caso negativo por separado (a costa de perder simplicidad).
Udayraj Deshmukh

9

Las otras respuestas han explicado en C99 o posterior, la división de enteros que implican operandos negativos siempre se trunca hacia cero .

Tenga en cuenta que, en C89 , si el resultado redondeado hacia arriba o hacia abajo está definido por la implementación. Debido a que (a/b) * b + a%bes igual aen todos los estándares, el resultado de %involucrar operandos negativos también se define en la implementación en C89.


5

¿Puede un módulo ser negativo?

%puede ser negativo ya que es el operador restante , el resto después de la división, no después de Euclidean_division . Desde C99 el resultado puede ser 0, negativo o positivo.

 // a % b
 7 %  3 -->  1  
 7 % -3 -->  1  
-7 %  3 --> -1  
-7 % -3 --> -1  

El módulo OP deseado es un módulo euclidiano clásico , no %.

Esperaba un resultado positivo cada vez.

Para realizar un módulo euclidiano que esté bien definido cada vez que a/bse define, a,bson de cualquier signo y el resultado nunca es negativo:

int modulo_Euclidean(int a, int b) {
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}

modulo_Euclidean( 7,  3) -->  1  
modulo_Euclidean( 7, -3) -->  1  
modulo_Euclidean(-7,  3) -->  2  
modulo_Euclidean(-7, -3) -->  2   

2

El resultado de la operación del módulo depende del signo del numerador y, por lo tanto, obtiene -2 para y y z

Aquí está la referencia

http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_14.html

División entera

Esta sección describe funciones para realizar la división de enteros. Estas funciones son redundantes en la biblioteca GNU C, ya que en GNU C el operador '/' siempre se redondea hacia cero. Pero en otras implementaciones de C, '/' puede redondearse de manera diferente con argumentos negativos. div e ldiv son útiles porque especifican cómo redondear el cociente: hacia cero. El resto tiene el mismo signo que el numerador.


55
Se está refiriendo a un texto sobre ANSI C. Esta es una norma bastante antigua de C. No estoy seguro si el texto es correcto con respecto a ANSI C, pero definitivamente no con respecto a C99. En C99 §6.5.5, la división entera se define para truncarse siempre hacia cero.
Palec

2

En Matemáticas, de donde provienen estas convenciones, no hay afirmación de que la aritmética de módulo deba producir un resultado positivo.

P.ej.

1 mod 5 = 1, pero también puede ser igual a -4. Es decir, 1/5 produce un resto 1 de 0 o -4 de 5. (Ambos factores de 5)

Del mismo modo, -1 mod 5 = -1, pero también puede ser igual a 4. Es decir, -1/5 produce un resto -1 de 0 o 4 de -5. (Ambos factores de 5)

Para leer más, mire las clases de equivalencia en matemáticas.


La clase de equivalencia es un concepto diferente y el módulo se define de una manera muy estricta. Digamos que tenemos dos números enteros ay b, b <> 0. Según el teorema de la división euclidiana, existe exactamente un par de enteros m, rdonde a = m * b + ry 0 <= r < abs( b ). Lo dicho res el resultado de la operación del módulo (matemático) y, por definición, no es negativo. Más lecturas y más enlaces en Wikipedia: en.wikipedia.org/wiki/Euclidean_division
Ister

Eso no es verdad. 1 mod 5siempre es 1. también -4 mod 5puede ser 1, pero son cosas diferentes.
FelipeC

2

De acuerdo con el estándar C99 , sección 6.5.5 Operadores multiplicativos , se requiere lo siguiente:

(a / b) * b + a % b = a

Conclusión

El signo del resultado de una operación restante, según C99, es el mismo que el del dividendo.

Veamos algunos ejemplos ( dividend / divisor):

Cuando solo el dividendo es negativo

(-3 / 2) * 2  +  -3 % 2 = -3

(-3 / 2) * 2 = -2

(-3 % 2) must be -1

Cuando solo el divisor es negativo

(3 / -2) * -2  +  3 % -2 = 3

(3 / -2) * -2 = 2

(3 % -2) must be 1

Cuando tanto el divisor como el dividendo son negativos

(-3 / -2) * -2  +  -3 % -2 = -3

(-3 / -2) * -2 = -2

(-3 % -2) must be -1

6.5.5 Operadores multiplicativos

Sintaxis

  1. expresión multiplicativa:
    • cast-expression
    • multiplicative-expression * cast-expression
    • multiplicative-expression / cast-expression
    • multiplicative-expression % cast-expression

Restricciones

  1. Cada uno de los operandos tendrá tipo aritmético. Los operandos del operador % deberán tener un tipo entero.

Semántica

  1. Las conversiones aritméticas habituales se realizan en los operandos.

  2. El resultado del operador binario * es el producto de los operandos.

  3. El resultado del operador / es el cociente de la división del primer operando por el segundo; El resultado del % operador es el resto. En ambas operaciones, si el valor del segundo operando es cero, el comportamiento es indefinido.

  4. Cuando se dividen los enteros, el resultado del operador / es el cociente algebraico con cualquier parte fraccional descartada [1]. Si el cociente a/bes representable, la expresión (a/b)*b + a%bserá igual a.

[1]: Esto a menudo se llama "truncamiento hacia cero".


1

El operador de módulo da el resto. El operador de módulo en c generalmente toma el signo del numerador

  1. x = 5% (-3): aquí el numerador es positivo, por lo tanto, da como resultado 2
  2. y = (-5)% (3): aquí el numerador es negativo, por lo tanto, resulta -2
  3. z = (-5)% (-3): aquí el numerador es negativo, por lo tanto, resulta -2

Además, el operador de módulo (resto) solo se puede usar con tipo entero y no se puede usar con coma flotante.


2
Sería bueno si puede respaldar esto con enlaces a recursos externos.
J ... S

1

Creo que es más útil pensar modque se define en aritmética abstracta; no como una operación, sino como una clase de aritmética completamente diferente, con diferentes elementos y diferentes operadores. Eso significa que la suma mod 3no es lo mismo que la suma "normal"; es decir; suma entera.

Entonces cuando lo haces:

5 % -3

Está intentando asignar el número entero 5 a un elemento en el conjunto de mod -3. Estos son los elementos de mod -3:

{ 0, -2, -1 }

Entonces:

0 => 0, 1 => -2, 2 => -1, 3 => 0, 4 => -2, 5 => -1

Digamos que tiene que permanecer despierto por alguna razón 30 horas, ¿cuántas horas le quedan de ese día? 30 mod -24.

Pero lo que C implementa no es mod, es un resto. De todos modos, el punto es que tiene sentido devolver negativos.

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.