sumas suma resuelve restas resta que primero orden operaciones numeros multiplicaciones multiplicacion matematicas luego llaman las julioprofe ejercicios divisiones cual complejos como c++ compiler-optimization

c++ - resuelve - suma resta multiplicacion y division como se llaman



¿Cómo puedo preservar el orden de las multiplicaciones y divisiones? (4)

En mi aplicación C ++ incrustada de 32 bits, necesito realizar el siguiente cálculo:

calc(int milliVolts, int ticks) { return milliVolts * 32767 * 65536 / 1000 / ticks; }

Ahora, dado que un int en mi plataforma tiene 32 bits y milliVolts tiene un rango de [-1000: 1000], la parte de milliVolts * 32767 * 65536 puede causar un desbordamiento de entero. Para evitar esto, he dividido el factor 65536 en 1024, 32 y reordenado de la siguiente manera:

calc(int milliVolts, int ticks) { return milliVolts*32767*32/1000*1024/ticks*2; }

De esta manera, siempre que el compilador conserve el orden de las multiplicaciones y divisiones, la función se calculará correctamente.

Kerninghan y Ritchie establecen en la sección 2.12 de "El lenguaje de programación C" (no tengo una copia del estándar de C ++ a mano):

C, como la mayoría de los idiomas, no especifica el orden en que se evalúan los operandos de un operador.

Si entiendo esto correctamente, el compilador es libre de cambiar mi expresión cuidadosamente elegida en algo que no funcionará como se esperaba.

¿Cómo puedo escribir mi función de manera que se garantice que funcione?

EDITAR: varias respuestas a continuación sugieren el uso de cálculos de punto flotante para evitar este problema. Esta no es una opción porque el código se ejecuta en una CPU que no tiene operaciones de punto flotante. Además, el cálculo se encuentra en la parte más difícil en tiempo real de mi aplicación, por lo que la penalización de velocidad por usar un punto flotante emulado es demasiado grande.

CONCLUSIÓN: Con la ayuda de la respuesta de Merdad y el comentario de Matt McNabb, logré encontrar la sección correspondiente en K&R, sección A7, donde dice:

La precedencia y la asociatividad de los operadores está completamente especificada, pero el orden de evaluación de las expresiones es, con ciertas excepciones, indefinido, incluso si las subexpresiones involucran efectos secundarios. Es decir, a menos que la definición de un operador garantice que sus operandos se evalúen en un orden particular, la implementación es libre de evaluar los operandos en cualquier orden, o incluso de intercalar su evaluación. Sin embargo, cada operador combina los valores producidos por sus operandos de una manera compatible con el análisis de las expresiones en las que aparece. Esta regla revoca la libertad anterior para reordenar expresiones con operadores matemáticamente conmutativos y asociativos, pero puede dejar de ser computacionalmente asociativo. El cambio solo afecta a los cálculos de punto flotante cerca de los límites de su precisión y a las situaciones donde es posible el desbordamiento.

Así que Merdad tiene razón: no hay nada de qué preocuparse.


Actualmente:

Usted (y los demás) malinterpretan lo que se dice. No hay ningún problema aquí.

Dicen que si tiene f(x) + g(y) , no hay garantía de que f(x) se evalúe antes que g(y) (o viceversa).

Pero si tienes

milliVolts * 32767 * 32 / 1000 * 1024 / ticks * 2

se interpreta automáticamente como

(((((milliVolts * 32767) * 32) / 1000) * 1024) / ticks) * 2

lo que significa que evaluar los lados izquierdo y derecho de cualquier operador fuera de orden no generará ningún problema, ya que todas las expresiones del lado derecho de un operador son variables o números, en ambos casos la evaluación del lado derecho es un no-op (no tiene efectos secundarios, al contrario de una llamada de función).

Por lo tanto, no hay nada de qué preocuparse.


Mi respuesta sería emitir milivoltios y tics a int64_t y realizar los cálculos. Luego afirmar si los resultados se pueden almacenar en el int .

EDITAR: datahaki sugiere utilizar valores de punto flotante en el cálculo. Esto puede resultar útil porque lo más probable es que termines calculando con fracciones.


Siempre vas a tener un cierto error de redondeo, creo que no necesita explicación. Entonces, la estrategia es intentar minimizar el error tanto como sea posible, teniendo en cuenta que no conocemos a priori los valores de milivoltios y tics.

Sugiero dividir el cálculo en dos etapas. Primero, agrupando sus constantes tenemos: 32767 * 65536/1000 = 2147418.112 = 2147418, con un error de 0.112, que es solo 1 parte en 20 millones, aproximadamente.

Así que declara una const. Int.

const int factor = 2147418;

Ahora, los milivoltios están en el rango [-1000,1000] y las marcas en el rango [1,1024]. Si calculamos milivoltios / tics , podemos tener grandes errores; Consideremos por ejemplo la situación:

millivolts=1; ticks=1024; intermediate = millivolts/ticks = 0; result = intermediate * factor = 0; /// Big error, as result should be 2097

Así que sugiero lo siguiente:

int intermediate = factor * millivolt; int result = intermediate / ticks;

De esta manera, en el peor de los casos ( milivoltios = 1000), los ajustes intermedios en un entero de 32 bits, que, al carecer de cualquier información en contra, supondré que puede usarlos.


(Esto es más un comentario, pero fue demasiado largo para caber en un campo de comentarios)

"Usaré paréntesis. Incluso para aclarar el código".

Sí, sí y otro sí, por favor. Si bien se ha demostrado que su expresión no es ambigua para el compilador, aún puede causar confusión a los ojos del lector ocasional. Sé que algunas personas pueden recordar todas las reglas de precedencia, pero a menudo me encuentro con que tengo que buscar este tipo de cosas. Los paréntesis agregados recorrerán un largo camino para que quede claro para el lector.

Pero lo más importante, agregue un comentario que explique exactamente por qué tiene que realizar su cálculo de esta manera. Este es exactamente el tipo de situación en la que aparece un futuro mantenedor con buenas intenciones y trata de "simplificar" su código. Un comentario bien colocado ayudaría a defenderse de tales refactorizaciones no deseadas.