Cómo codificar un operador de módulo (%) en C / C ++ / Obj-C que maneja números negativos

86

Uno de mis odios favoritos de los lenguajes derivados de C (como matemático) es que

(-1) % 8 // comes out as -1, and not 7

fmodf(-1,8) // fails similarly

Cual es la mejor solucion?

C ++ permite la posibilidad de sobrecargar las plantillas y el operador, pero ambos son aguas turbias para mí. ejemplos recibidos con gratitud.

Pi
fuente
1
No creo que esto sea un "duplicado" de stackoverflow.com/questions/828092/… según la definición oficial. No es cierto que las respuestas de esta pregunta se puedan fusionar con la de la otra, porque esta pregunta solo se refiere al módulo, no también a la división. Pero creo que esta pregunta está cubierta por esa, así que está cerca. Mi respuesta ya está ahí, FWIW.
Steve Jessop
Quizás ese hilo debería dividirse, ya que plantea dos preguntas distintas. la mejor manera de hacerlo podría ser volver a hacer la pregunta de división por separado y luego apuntar hacia esa respuesta. Se lo dejo a alguien que entienda mejor los mecanismos de este sitio web.
P i
3
@Pi owhere se %dice que es el módulo ... es el resto .
obataku
1
Aquí hay otro hilo que es un "duplicado" de: stackoverflow.com/questions/1082917/… Solo como referencia sobre este %problema.
leetNightshade
Si solo está dividiendo potencias de dos, entonces podría ser una mejor idea usar y:(-1) & 8 == 7
Henricus V.

Respuestas:

74

En primer lugar, me gustaría señalar que ni siquiera se puede confiar en el hecho de que (-1) % 8 == -1. lo único en lo que puede confiar es en eso (x / y) * y + ( x % y) == x. Sin embargo, si el resto es negativo o no, está definido por la implementación .

Ahora, ¿por qué usar plantillas aquí? Una sobrecarga de ints y longs bastaría.

int mod (int a, int b)
{
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

y ahora puedes llamarlo como mod (-1,8) y parecerá 7.

Editar: encontré un error en mi código. No funcionará si b es negativo. Entonces creo que esto es mejor:

int mod (int a, int b)
{
   if(b < 0) //you can check for b == 0 separately and do what you want
     return -mod(-a, -b);   
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

Referencia: C ++ 03 párrafo 5.6 cláusula 4:

El operador binario / produce el cociente y el operador binario% produce el resto de la división de la primera expresión por la segunda. Si el segundo operando de / o% es cero, el comportamiento no está definido; de lo contrario (a / b) * b + a% b es igual a a. Si ambos operandos son no negativos, el resto no es negativo; si no, el signo del resto está definido por la implementación .

Armen Tsirunyan
fuente
2
@Ohmu: Sí, eso está en el estándar C ++. <quote> Para operandos integrales, el operador / produce el cociente algebraico con cualquier parte fraccionaria descartada; si el cociente a / b es representable en el tipo de resultado, (a / b) * b + a% b es igual a a. </quote>
Ben Voigt
5
-1. Han pasado 11 años desde que se definió la implementación. ISO 9899: 1999 lo definió y, lamentablemente, eligió la mala definición.
R .. GitHub DEJA DE AYUDAR A ICE
3
@Armen: Borró convenientemente la nota al pie <quote> ... la división de enteros sigue las reglas definidas en el estándar ISO Fortran, ISO / IEC 1539: 1991, en el que el cociente siempre se redondea hacia cero </quote>. El nuevo estándar C ++ actualiza este comportamiento de "preferido" a obligatorio, al igual que Fortran y C.
Ben Voigt
2
@Armen: La especificación anterior está rota, pero la rotura es diferente del problema de la señal, y es fácil pasarla por alto hasta que miras la nueva redacción. C ++ 03 no tenía "si el cociente a / b es representable en el tipo de resultado", lo que causa problemas para INT_MIN / -1(en implementaciones de complemento a dos). Bajo la antigua especificación, es -32768 % -1posible que tenga que evaluar a -65536(que tampoco está en el rango del tipo de 16 bits, ¡puaj!) Para que la identidad se mantenga.
Ben Voigt
1
re "Sin embargo, si el resto es negativo o no, está definido por la implementación", C ++ 11 garantiza que la división de enteros se redondea hacia 0.
Saludos y hth. - Alf
12

Aquí hay una función C que maneja valores enteros positivos O negativos O fraccionarios para AMBOS OPERANDOS

#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)

Esta es seguramente la solución más elegante desde un punto de vista matemático. Sin embargo, no estoy seguro de si es robusto en el manejo de números enteros. A veces, los errores de punto flotante se infiltran al convertir int -> fp -> int.

Estoy usando este código para no int s, y una función separada para int.

NOTA: ¡es necesario atrapar N = 0!

Código del probador:

#include <math.h>
#include <stdio.h>

float mod(float a, float N)
{
    float ret = a - N * floor (a / N);

    printf("%f.1 mod %f.1 = %f.1 \n", a, N, ret);

    return ret;
}

int main (char* argc, char** argv)
{
    printf ("fmodf(-10.2, 2.0) = %f.1  == FAIL! \n\n", fmodf(-10.2, 2.0));

    float x;
    x = mod(10.2f, 2.0f);
    x = mod(10.2f, -2.0f);
    x = mod(-10.2f, 2.0f);
    x = mod(-10.2f, -2.0f);

    return 0;
}

(Nota: puede compilarlo y ejecutarlo directamente desde CodePad: http://codepad.org/UOgEqAMA )

Salida:

fmodf (-10.2, 2.0) = -0.20 == ¡FALLO!

10.2 mod 2.0 = 0.2
10.2 mod -2.0 = -1.8
-10.2 mod 2.0 = 1.8
-10.2 mod -2.0 = -0.2

Pi
fuente
Desafortunadamente, esto no funciona con números enteros. Deberían convertirse a punto flotante antes de la división para permitir su uso floor(). Además, es posible que pierda precisión cuando se convierte en flotante: pruebe (float)1000000001/3, ¡se sorprenderá de los resultados!
cmaster - reinstalar a monica
9

Acabo de notar que Bjarne Stroustrup etiqueta %como el operador restante , no como el operador de módulo.

Apostaría a que este es su nombre formal en las especificaciones ANSI C & C ++, y que el abuso de la terminología se ha infiltrado. ¿Alguien sabe esto a ciencia cierta?

Pero si este es el caso, entonces la función fmodf () de C (y probablemente otras) son muy engañosas. deben estar etiquetados como fremf (), etc.

Pi
fuente
1
El estándar C11 (o el borrador público final para ser exactos) menciona "módulo" seis veces, pero solo en relación con la representación de varios tipos. Ni una sola vez menciona "módulo" en relación con el operador restante ( %).
Nisse Engström
7

La función general más simple para encontrar el módulo positivo sería la siguiente: funcionaría tanto en valores positivos como negativos de x.

int modulo(int x,int N){
    return (x % N + N) %N;
}
Udayraj Deshmukh
fuente
6

Para enteros, esto es simple. Solo haz

(((x < 0) ? ((x % N) + N) : x) % N)

donde estoy suponiendo que Nes positivo y representable en el tipo de x. Su compilador favorito debería poder optimizar esto, de modo que termine en una sola operación de modificación en ensamblador.

Jens Gustedt
fuente
3
No funciona: porque int x=-9001; unsigned int N=2000;da 2295, no 999.
Hubert Kario
1
@HubertKario ¿Quizás comprobar de nuevo? No hay forma de que algo módulo 2000 dé 2295, debe haber cometido un error.
sam hocevar
2
@SamHocevar: Creo que el problema aquí son las extrañas reglas de promoción de enteros C. promocionar firmado a unsigned y promover un valor entero con signo negativo a unsigned invoca un comportamiento indefinido en C.
datenwolf
1
Creo que una forma mucho más simple (y más eficiente) sería: (x < 0) ? (x % N + N) : (x % N).
Chris Nolet
3

La mejor solución para un matemático es usar Python.

La sobrecarga del operador C ++ tiene poco que ver con esto. No puede sobrecargar operadores para tipos integrados. Lo que quieres es simplemente una función. Por supuesto, puede usar plantillas de C ++ para implementar esa función para todos los tipos relevantes con solo 1 pieza de código.

La biblioteca C estándar proporciona fmod, si recuerdo el nombre correctamente, para tipos de punto flotante.

Para enteros, puede definir una plantilla de función C ++ que siempre devuelva un resto no negativo (correspondiente a la división euclidiana) como ...

#include <stdlib.h>  // abs

template< class Integer >
auto mod( Integer a, Integer b )
    -> Integer
{
    Integer const r = a%b;
    return (r < 0? r + abs( b ) : r);
}

... y simplemente escribe en mod(a, b)lugar de a%b.

Aquí el tipo Integerdebe ser un tipo entero con signo.

Si desea el comportamiento matemático común donde el signo del resto es el mismo que el signo del divisor, puede hacerlo, por ejemplo,

template< class Integer >
auto floor_div( Integer const a, Integer const b )
    -> Integer
{
    bool const a_is_negative = (a < 0);
    bool const b_is_negative = (b < 0);
    bool const change_sign  = (a_is_negative != b_is_negative);

    Integer const abs_b         = abs( b );
    Integer const abs_a_plus    = abs( a ) + (change_sign? abs_b - 1 : 0);

    Integer const quot = abs_a_plus / abs_b;
    return (change_sign? -quot : quot);
}

template< class Integer >
auto floor_mod( Integer const a, Integer const b )
    -> Integer
{ return a - b*floor_div( a, b ); }

… Con la misma restricción Integer, que es un tipo firmado.


¹ Porque la división de enteros de Python se redondea hacia el infinito negativo.

Saludos y hth. - Alf
fuente
su código parece tener el mismo error que el mío antes de mi edición. ¿Y si b es negativo? :)
Armen Tsirunyan
1
@Armen: ¡gracias! pero soy demasiado vago para editar solo por eso ... :-)
Saludos y hth. - Alf
@ArmenTsirunyan: el rresultado debe hacer a= r + b*(a/b)verdadero. no importa cómo se implemente la división de enteros, b*somethinges un múltiplo de b. esto hace run resultado de módulo válido incluso si es negativo. puede agregarle abs ( b) y seguirá siendo un resultado de módulo válido.
Saludos y hth. - Alf
2
@downvoters: esta respuesta sigue siendo correcta, mientras que la "solución" seleccionada ahora contiene comentarios incorrectos debido a las nuevas garantías en C ++ 11. Es bastante irónico rechazar una respuesta que sigue siendo correcta. Sin dar ninguna razón, uno tiene que asumir que al menos 2 personas asociativas, con un grado casi absoluto de ignorancia, leyeron el comentario de esta pregunta y, en forma instintiva, asociativamente votaron negativamente. Explique sus votos negativos.
Saludos y hth. - Alf
1
El resultado matemáticamente deseado es que el resto sea cero o que tenga el mismo signo que el divisor (denominador). Si el divisor es negativo, el resto debe ser cero o negativo. La implementación de C / C ++ da como resultado que el resto sea cero o tenga el mismo signo que el dividendo (numerador).
rcgldr
2

Oh, también odio% design para esto ...

Puede convertir dividendos a sin firmar de una manera como:

unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider

result = (offset + dividend) % divider

donde el desplazamiento es el más cercano a (-INT_MIN) múltiplo de módulo, por lo que sumarlo y restarlo no cambiará el módulo. Tenga en cuenta que tiene un tipo sin firmar y el resultado será un número entero. Desafortunadamente, no puede convertir correctamente los valores INT_MIN ... (- offset-1) ya que provocan un desbordamiento arifmético. Pero este método tiene la ventaja de solo una aritmética adicional por operación (y sin condicionales) cuando se trabaja con un divisor constante, por lo que se puede usar en aplicaciones similares a DSP.

Hay un caso especial, donde el divisor es 2 N (potencia entera de dos), para el cual el módulo se puede calcular usando aritmética simple y lógica bit a bit como

dividend&(divider-1)

por ejemplo

x mod 2 = x & 1
x mod 4 = x & 3
x mod 8 = x & 7
x mod 16 = x & 15

Una forma más común y menos complicada es obtener el módulo usando esta función (funciona solo con divisor positivo):

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

Este solo resultado correcto si es negativo.

También puedes engañar:

(p% q + q)% q

Es muy corto pero usa dos% -s que normalmente son lentos.

Vovanium
fuente
2

Creo que otra solución a este problema se usaría para variables de tipo long en lugar de int.

Estaba trabajando en un código en el que el operador% devolvía un valor negativo que causaba algunos problemas (para generar variables aleatorias uniformes en [0,1] realmente no desea números negativos :)), pero después de cambiar las variables a type long, todo funcionaba sin problemas y los resultados coincidían con los que obtenía al ejecutar el mismo código en python (importante para mí, ya que quería poder generar los mismos números "aleatorios" en varias plataformas.

david
fuente
2

Aquí hay una nueva respuesta a una pregunta anterior, basada en este artículo de Microsoft Research y sus referencias.

Tenga en cuenta que desde C11 y C ++ 11 en adelante, la semántica de se divha convertido en un truncamiento hacia cero (ver [expr.mul]/4). Además, para Ddividir por d, C ++ 11 garantiza lo siguiente sobre el cociente qTy el restorT

auto const qT = D / d;
auto const rT = D % d;
assert(D == d * qT + rT);
assert(abs(rT) < abs(d));
assert(signum(rT) == signum(D));

donde se signumasigna a -1, 0, +1, dependiendo de si su argumento es <, ==,> que 0 (consulte estas preguntas y respuestas para obtener el código fuente).

Con división truncada, el signo del resto es igual al signo del dividendoD , es decir -1 % 8 == -1. C ++ 11 también proporciona una std::divfunción que devuelve una estructura con miembros quotyrem acuerdo con la división truncada.

Hay otras definiciones posibles, por ejemplo, la denominada división en suelo se puede definir en términos de la división truncada incorporada.

auto const I = signum(rT) == -signum(d) ? 1 : 0;
auto const qF = qT - I;
auto const rF = rT + I * d;
assert(D == d * qF + rF);
assert(abs(rF) < abs(d));
assert(signum(rF) == signum(d));

Con división en suelo, el signo del resto es igual al signo del divisor.d . En lenguajes como Haskell y Oberon, hay operadores integrados para la división por suelo. En C ++, necesitaría escribir una función usando las definiciones anteriores.

Otra forma más es la división euclidiana , que también se puede definir en términos de la división truncada incorporada.

auto const I = rT >= 0 ? 0 : (d > 0 ? 1 : -1);
auto const qE = qT - I;
auto const rE = rT + I * d;
assert(D == d * qE + rE);
assert(abs(rE) < abs(d));
assert(signum(rE) != -1);

Con la división euclidiana, el signo del resto es siempre positivo .

TemplateRex
fuente
2

Para una solución que no usa ramas y solo 1 mod, puede hacer lo siguiente

// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
  return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}
Kyle Butt
fuente
1
/ * Advertencia: la macro mod evalúa los efectos secundarios de sus argumentos varias veces. * /
#definir mod (r, m) (((r)% (m)) + ((r) <0)? (m): 0)

... o simplemente acostumbrarse a obtener cualquier representante para la clase de equivalencia.

Eric Towers
fuente
2
"¿Acostumbrarse a tener algún representante para la clase de equivalencia"? Eso es una tontería. Si lo desea, puede utilizar el "representante" original r. El %operador no tiene nada que ver con las clases de equivalencia. Es el operador del resto y el resto está bien definido algebraicamente como no negativo y menor que el divisor. Lamentablemente, C lo definió de manera incorrecta. Aún así, +1 por tener una de las mejores respuestas.
R .. GitHub DEJA DE AYUDAR A ICE
0

Plantilla de ejemplo para C ++

template< class T >
T mod( T a, T b )
{
    T const r = a%b;
    return ((r!=0)&&((r^b)<0) ? r + b : r);
}

Con esta plantilla, el resto devuelto será cero o tendrá el mismo signo que el divisor (denominador) (el equivalente al redondeo hacia el infinito negativo), en lugar de que el comportamiento de C ++ del resto sea cero o tenga el mismo signo que el dividendo ( numerador) (el equivalente a redondear hacia cero).

rcgldr
fuente
-1
unsigned mod(int a, unsigned b) {
    return (a >= 0 ? a % b : b - (-a) % b);
}
Neuer
fuente
-1

Esta solución (para usar cuando modes positiva) evita tomar operaciones negativas de división o resto todas juntas:

int core_modulus(int val, int mod)
{
    if(val>=0)
        return val % mod;
    else
        return val + mod * ((mod - val - 1)/mod);
}
Robotbugs
fuente
-2

Yo lo haría:

((-1)+8) % 8 

Esto agrega el último número al primero antes de hacer el módulo dando 7 como se desee. Esto debería funcionar para cualquier número hasta -8. Para -9 agregue 2 * 8.

Toby O'Connell
fuente
2
¿Y para una variable cuyo valor podría ser -99999?
Keith Thompson