Comportamiento inesperado con pow (2, x) en Embedded C

2

Soy un novato en Embedded C (aprendiz solo). Lo estoy usando para conducir alrededor de un robot (usando el ATmega2560 con 14.7456 MHz). Estoy experimentando un comportamiento inesperado con el siguiente código:

int j = 2;

PORTJ = pow(2, j) - 1;

Ahora espero que se vea 3 en el puerto J. Sin embargo, veo 2. Además, si hago esto:

PORTJ = pow(2, 2) - 1;

entonces funciona bien (es decir, veo 3 en el puerto J). La razón por la que quiero hacer esto usando una variable es porque mi código real es el siguiente:

int j = 0;

while(1)
{   
    j = (j + 1) % 9;

    PORTJ = pow(2, j) - 1;

    _delay_ms(500);
}

Esto funciona para todos los valores de J excepto para j = 2. ¿Qué estoy haciendo mal aquí? Recientemente empecé, así que no sé mucho sobre esto. Cualquier ayuda es apreciada.

    
pregunta Rohan Saxena

3 respuestas

15

Si bien esta es una pregunta de programación, puede aparecer con bastante frecuencia en configuraciones integradas, por lo que no es un tema fuera de tema aquí.

El problema más probable es, en efecto, causado por truncamiento (no redondeo, eso no ocurre).

Un enfoque mucho mejor sería usar un operador de cambio de bits: esto no solo será más fiel a su intención, sino también más rápido, ya que en circunstancias básicas es una operación primitiva implementada directamente en muchas ALU (aunque no, como señala el tubo, el AVR, que solo puede cambiar una posición de bit a la vez)

int j = 2;

PORTJ = (1 << j) - 1

(EDITAR - gracias a ilkkachu por la corrección)

Encontrará estas operaciones en todo el lugar en macros de E / S para muchos microcontroladores y hardware o código relacionado con el campo de bits en sistemas más grandes.

(Por cierto, si intentas usar esto para cambiar o producir un valor mayor que un int , busca las reglas de tipo que correspondan, ¡no son necesariamente intuitivas!)

    
respondido por el Chris Stratton
1

El problema es que la función pow desafortunadamente solo se especifica para números de punto flotante. Y los números de punto flotante tienen inexactitud, vea esto .

Entonces, cuando haces 2 ^ 2 puede que no obtengas exactamente 4, pero tal vez 3.9999. Ese es el error real. Entonces 3.9999 - 1 es 2.9999. Cuando lo conviertas de nuevo a un tipo entero para imprimirlo en el puerto, se truncará a 2.

Para empezar, no debería utilizar el punto flotante para empezar, ya que el uso de números de punto flotante en los sistemas integrados es casi siempre un mal diseño. Sólo los programas que usan los cálculos de punto flotante ampliamente deberían necesitarlos. Por ejemplo, si realiza muchos cálculos de trigonometría, procesamiento de señales, lógica difusa, IA o aplicaciones de casos especiales similares.

¡En este caso, eligió la MCU equivocada por completo, ya que una MCU de 8 bits sin FPU en el chip será extremadamente ineficaz para procesar dichos cálculos! No es una PC.

Solo olvídate de los números flotantes. Es trivial escribir una versión entera de pow () usted mismo:

uint32_t intpow (uint32_t base, uint32_t exp)
{
  uint32_t sum;

  if(exp == 0)
  {
    sum = 1;
  }
  else
  {
    sum = base;
    for(uint32_t i=1; i<exp; i++)
    {
      sum *= base;
    }
  }

  return sum;
}

En caso de que no necesites grandes números, puedes optimizar un poco lo de arriba bajando a uint16_t o uint8_t , que tu MCU 8-bitter disfrutará mucho más. Y como se señaló en otras respuestas, cualquier cosa 2^x podría resolverse con cambios de bits.

También, como se mencionó en otra respuesta, considere usar tablas de búsqueda con valores precalculados, si esa es una opción.

    
respondido por el Lundin
0

Para este sencillo ejemplo, haría una tabla (matriz) de valores int que coincidan con el ancho de bits de su PORTJ. Esta tabla tendría diez valores indexados de 0 a 9.

Dentro del bucle, la asignación al puerto simplemente se convierte en:

PORTJ = array[j];

Esto será rápido y será un código muy pequeño.

Si las entradas en la tabla (matriz) se convierten en verdaderos cálculos complejos, hágalos en una hoja de cálculo y exporte los resultados a su archivo de código C.

    
respondido por el Michael Karas

Lea otras preguntas en las etiquetas