¿Por qué la división de módulo (%) solo funciona con números enteros?

79

Recientemente me encontré con un problema que podría resolverse fácilmente usando la división de módulo, pero la entrada era un flotador:

Dada una función periódica (p sin. Ej. ) Y una función de computadora que solo puede calcularla dentro del rango del período (p. Ej. [-Π, π]), cree una función que pueda manejar cualquier entrada.

La solución "obvia" es algo como:

#include <cmath>

float sin(float x){
    return limited_sin((x + M_PI) % (2 *M_PI) - M_PI);
}

¿Por qué no funciona esto? Me sale este error:

error: invalid operands of types double and double to binary operator %

Curiosamente, funciona en Python:

def sin(x):
    return limited_sin((x + math.pi) % (2 * math.pi) - math.pi)
Brendan Long
fuente
20
π no es igual a 3,14 y, de hecho, no se puede representar como ningún tipo de punto flotante. Calcular sin(x)valores grandes de en xrealidad requiere un proceso de reducción de argumentos trascendentales muy difícil que no puede funcionar con ninguna aproximación finita de pi.
R .. GitHub DEJA DE AYUDAR A ICE
3
Es casi seguro que se trata de una tarea para el hogar, por lo que los errores de punto flotante están fuera del alcance de la tarea o tiene la intención de conducir a una discusión de análisis numérico más riguroso. De cualquier manera, fmodes probable que el instructor esté buscando.
Dennis Zickefoose
2
No es tarea, es solo algo que surgió al leer otra pregunta SO ( stackoverflow.com/questions/6091837/… )
Brendan Long
2
Bien, debería haber sido más preciso en mi declaración. Mi punto fue que si el argumento puede crecer sin límites (no solo el tamaño del exponente de doble precisión), ninguna aproximación finita de pi será suficiente. Para el doble, sí, una aproximación muy larga de pi será suficiente.
R .. GitHub DEJA DE AYUDAR A ICE
1
@aschepler: No creo que haya entendido el problema.
R .. GitHub DEJA DE AYUDAR A ICE

Respuestas:

76

Porque la noción matemática normal de "resto" solo es aplicable a la división de enteros. es decir, la división que se requiere para generar un cociente entero.

Para extender el concepto de "resto" a números reales, debe introducir un nuevo tipo de operación "híbrida" que generaría cocientes enteros para operandos reales . El lenguaje Core C no admite dicha operación, pero se proporciona como una fmodfunción de biblioteca estándar , así como también remainderfunciona en C99. (Tenga en cuenta que estas funciones no son las mismas y tienen algunas peculiaridades. En particular, no siguen las reglas de redondeo de la división de enteros).

Hormiga
fuente
7
De valor, según la definición de% en el estándar 98: "(a / b) * b + a% b es igual a a". Para los tipos de coma flotante, (a/b)*bya es igual a[en la medida en que se pueda hacer tal declaración para tipos de coma flotante], por a%blo que nunca sería particularmente útil.
Dennis Zickefoose
1
@Dennis: De hecho, algebraicamente, en un campo el resto es siempre 0. La definición más apropiada del %operador para punto flotante, supongo, sería a-(a/b)*b, que sería 0 o un valor muy pequeño.
R .. GitHub DEJA DE AYUDAR A ICE
7
@Dennis: Puede arreglar fácilmente esta fórmula requiriendo que "piso (a / b) * b + a% b = a". Tenga en cuenta que para los números enteros, floor (a / b) = a / b.
vog
La división de enteros estilo C usa trunc, no floor, pero el punto permanece.
dan04
18
-1 Re "la noción matemática normal de" resto "sólo es aplicable a la división de enteros", la noción matemática de aritmética de módulo funciona también para valores de punto flotante, y este es uno de los primeros temas que Donald Knuth discute en su clásico The Arte de la programación informática (tomo I). Es decir, alguna vez fue conocimiento básico. Hoy, los estudiantes no reciben la educación que pagan, en mi humilde opinión.
Saludos y hth. - Alf
52

Estás buscando fmod () .

Supongo que para responder más específicamente a su pregunta, en los lenguajes más antiguos, el %operador se definía simplemente como una división modular entera y en los lenguajes más nuevos decidieron ampliar la definición del operador.

EDITAR: Si tuviera que apostar a adivinar por qué, diría que es porque la idea de la aritmética modular se origina en la teoría de números y trata específicamente con números enteros.

Doug Stephen
fuente
9
"lenguajes más antiguos": APL se remonta a la década de 1960 y su operador de módulo "|" trabaja tanto con enteros como con datos de coma flotante (también con escalares, vectoriales, matrices, tensores, ...). No hay una buena razón por la que el operador de módulo "%" de C no pueda haber realizado la misma función que fmod si se usa con números de coma flotante.
rcgldr
@rcgldr Los objetivos de diseño no requerían módulo de coma flotante. C se implementó para compilar Unix y limitar la cantidad de lenguaje ensamblador necesario para el sistema operativo. "C es un lenguaje de procedimiento imperativo. Fue diseñado para ser compilado usando un compilador relativamente sencillo, para proporcionar acceso de bajo nivel a la memoria, para proporcionar construcciones de lenguaje que se mapean de manera eficiente a las instrucciones de la máquina y para requerir un soporte mínimo en tiempo de ejecución". en.wikipedia.org/wiki/C_(programming_language)
harper
1
@harper: dado que C incluye aritmética de punto flotante como sumar, restar, multiplicar y dividir, usando la misma sintaxis que usa para enteros, no veo por qué no podría haber incluido módulo usando la misma sintaxis (%) . La elección de incluirlo o no parece arbitraria.
rcgldr
16

Realmente no puedo decirlo con certeza , pero supongo que es principalmente histórico. Muchos de los primeros compiladores de C no admitían el punto flotante en absoluto. Se agregó más tarde, e incluso entonces no tan completamente: se agregó principalmente el tipo de datos y las operaciones más primitivas se admitieron en el lenguaje, pero todo lo demás se dejó a la biblioteca estándar.

Jerry Coffin
fuente
1
+1 por ser la primera respuesta razonable que veo mientras leo la lista. En realidad, habiéndolos leído todos, esta es la única respuesta razonable.
Saludos y hth. - Alf
Un +1 tardío de mi parte también. Solía ​​escribir en C para los sistemas integrados 6809 y Z80. De ninguna manera podría permitirme el espacio para incluir la biblioteca en tiempo de ejecución de c. Incluso tuve que escribir mi propio código de inicio. El punto flotante era un lujo que no podía permitirme :)
Richard Hodges
12

El operador de módulo %en C y C ++ está definido para dos enteros, sin embargo, hay una fmod()función disponible para su uso con dobles.

Mark Elliot
fuente
4
Esta es la respuesta a la pregunta de OP, pero ignora el problema fundamental de lo que OP está tratando de hacer: sin(fmod(x,3.14))o incluso sin(fmod(x,M_PI))no es igual a sin(x)valores grandes de x. De hecho, los valores pueden diferir hasta en 2,0.
R .. GitHub DEJA DE AYUDAR A ICE
2
@R ..: Correcto, pero esa es una pregunta diferente y no estoy del todo seguro de que haya una respuesta aceptada, aunque hay mucha investigación sobre el tema
Mark Elliot
@R - Arreglé la ecuación para hacerlo correctamente. La ecuación real no era el punto (fue bastante fácil de averiguar una vez que tuve la función para probarla).
Brendan Long
¿No es %el operador restante y no un operador de módulo?
chux - Reincorporación a Monica
7

Las limitaciones están en los estándares:

C11 (ISO / IEC 9899: 201x) §6.5.5 Operadores multiplicativos

Cada uno de los operandos tendrá un tipo aritmético. Los operandos del operador% deben ser de tipo entero.

C ++ 11 (ISO / IEC 14882: 2011) §5.6 Operadores multiplicativos

Los operandos de * y / deberán tener tipo aritmético o enumeración; los operandos de% tendrán tipo integral o enumeración. Las conversiones aritméticas habituales se realizan en los operandos y determinan el tipo de resultado.

La solución es usar fmod, que es exactamente la razón por la que los operandos de %están limitados al tipo entero en primer lugar, de acuerdo con C99 Justificación §6.5.5 Operadores multiplicativos :

El Comité C89 rechazó extender el operador% para trabajar en tipos flotantes, ya que tal uso duplicaría la instalación proporcionada por fmod

Yu Hao
fuente
2

El operador% le da un REMAINDER (otro nombre para módulo) de un número. Para C / C ++, esto solo se define para operaciones con números enteros. Python es un poco más amplio y le permite obtener el resto de un número de punto flotante para el resto de cuántas veces el número se puede dividir en él:

>>> 4 % math.pi
0.85840734641020688
>>> 4 - math.pi
0.85840734641020688
>>> 
Andrés
fuente
2
¡¡El resto no es 'otro nombre para módulo' !! Ver: stackoverflow.com/questions/13683563/… o de un math-pov: math.stackexchange.com/questions/801962/… En pocas palabras: módulo y resto son solo lo mismo para números positivos y otro ejemplo es que el resto no ' te dejo ir alrededor de la brújula (en sentido antihorario). Por favor corrija eso, ya que soy frugal para :P
votar en contra
2

El %operador no funciona en C ++, cuando intenta encontrar el resto de dos números que son del tipo Floato Double.

Por lo tanto, podría intentar usar la fmodfunción de math.h/ cmath.ho podría usar estas líneas de código para evitar usar ese archivo de encabezado:

float sin(float x) {
 float temp;
 temp = (x + M_PI) / ((2 *M_PI) - M_PI);
 return limited_sin((x + M_PI) - ((2 *M_PI) - M_PI) * temp ));

}

Caña
fuente
1

"La noción matemática de módulo aritmético también funciona para valores de coma flotante, y este es uno de los primeros temas que Donald Knuth discute en su clásico El arte de la programación informática (volumen I). Es decir, alguna vez fue conocimiento básico".

El operador de módulo de coma flotante se define de la siguiente manera:

m = num - iquot*den ; where iquot = int( num/den )

Como se indicó, la no operación del operador% en números de punto flotante parece estar relacionada con los estándares. El CRTL proporciona 'fmod', y generalmente también 'resto', para realizar% en números fp. La diferencia entre estos dos radica en cómo manejan el redondeo intermedio 'iquot'.

'resto' usa redondeo al más cercano y 'fmod' usa truncar simple.

Si escribe sus propias clases numéricas de C ++, nada le impide modificar el legado de no-op, al incluir un operador sobrecargado%.

Atentamente

Amor
fuente