c++ - restas - division sin operador
El módulo C++ requiere un molde de resta entre dos bytes*un*signed para funcionar, ¿por qué? (2)
El siguiente código de Arduino (C ++)
void setup()
{
Serial.begin(115200);
byte b1 = 12;
byte b2 = 5;
const byte RING_BUFFER_SIZE = 64;
byte diff = b2 - b1;
byte diff2 = (byte)(b2 - b1) % RING_BUFFER_SIZE; //<---NOTE HOW THE (byte) CAST IS *REQUIRED* TO GET THE RIGHT RESULT!!!!
Serial.println(b1);
Serial.println(b2);
Serial.println(RING_BUFFER_SIZE);
Serial.println(diff);
Serial.println(diff2);
}
void loop()
{
}
produce lo esperado:
12
5
64
249
57 //<--correct answer
Mientras que sin el molde "(byte)" como se muestra aquí:
void setup()
{
Serial.begin(115200);
byte b1 = 12;
byte b2 = 5;
const byte RING_BUFFER_SIZE = 64;
byte diff = b2 - b1;
byte diff2 = (b2 - b1) % RING_BUFFER_SIZE; //<---(byte) cast removed
Serial.println(b1);
Serial.println(b2);
Serial.println(RING_BUFFER_SIZE);
Serial.println(diff);
Serial.println(diff2);
}
void loop()
{
}
produce:
12
5
64
249
249 //<--wrong answer
¿Por qué la diferencia? ¿Por qué el operador de módulo SÓLO trabaja con el molde explícito?
Nota: "byte" = "uint8_t"
5 - 12
da -7
(un int
). Entonces tu código hace -7 % 64
.
Matemáticamente, esperaríamos que esto diera 57
. Sin embargo, en C y C ++, %
para números negativos no hace lo que cabría esperar matemáticamente. En cambio, cumple la siguiente ecuación:
(a/b) * b + a%b == a
Ahora, (-7)/64
da 0
porque C y C ++ usan truncamiento-hacia-cero para la división entera de negativos-positivos. Por lo tanto, -7 % 64
evalúa a -7
.
Finalmente, convirtiendo -7
en uint8_t
da 249
.
Cuando escribes (byte)-7 % 64
, en realidad estás haciendo 249 % 64
dando la respuesta esperada.
Con respecto al comportamiento de b2 - b1
: toda la aritmética de números enteros se realiza al menos con precisión int
; para cada operando de -
, si es un tipo entero más estrecho que int
, primero se promueve a int
(dejando el valor sin cambios). Se pueden producir más conversiones si los tipos difieren después de esta promoción (lo que no ocurre en este caso).
En el código, b2 - b1
significa (int)b2 - (int)b1
produciendo un int
; no hay forma de especificar hacer la operación con menor precisión.
Las operaciones aritméticas quieren operar en un int
o más grande. Por lo tanto, los byte
se promocionan a números enteros antes de que se restan, y es probable que obtengan int
reales, lo cual es correcto para C / C ++ porque pueden contener todo el rango de byte
.
Si el resultado de la resta se vuelve a convertir en byte
, le da el comportamiento de desbordamiento esperado. Sin embargo, si omite el molde en el cálculo de diff2
, está haciendo el módulo en un int
negativo . Y, dado que C / C ++ firmó rondas de división hacia cero, el módulo firmado tiene el mismo signo que el dividendo.
El primer paso en falso aquí es esperar que la sustracción actúe directamente en su tipo de byte
, o que traduzca su byte
sin signo en un int
sin signo. El problema de la cascada es pasar por alto el comportamiento de la división con signo de C ++ (lo que es comprensible si no sabes que deberías esperar que la aritmética con signo sea un problema en primer lugar).
Tenga en cuenta que, si su RING_BUFFER_SIZE
no fuera una potencia de dos, la división no funcionaría correctamente para casos como este de todos modos. Y, dado que es un poder de dos, tenga en cuenta que:
(b2 - b1)&(RING_BUFFER_SIZE-1)
debería funcionar correctamente
Y finalmente (como se sugiere en el comentario), la forma correcta de hacer un restar el búfer de anillo sería asegurarse de que b1 < RING_BUFFER_SIZE
(lo cual tenga sentido para una operación de búfer de anillo), y use algo como:
(b2>b1)? b2 - b1 : RING_BUFFER_SIZE + b2 - b1