Является ли странная проблема с сопоставлением поплавков?

У меня есть прерывание таймера, которое управляет шаговым двигателем на плате UNO. В обработчике прерываний он проверяет скорость двигателя и сравнивает его с целевой скоростью. Это позволяет двигателю ускоряться до целевой скорости неблокирующим способом.

Это соответствующая часть обработчика прерываний:

 ISR(TIMER1_COMPA_vect){
    if (targetSpeed > 0 || motorSpeed > 0){
        // leading edge of the step cycle
        if (leadingEdge){
            digitalWrite(STEP_PIN, HIGH);
            stepNumber++;
            // check speed and adjust if necesary
            if (motorSpeed < targetSpeed){
                motorSpeed += speedIncrement;
            }
            else if (motorSpeed > targetSpeed){
                motorSpeed -= speedIncrement;
            }

            leadingEdge = false;
        }
        // trailing edge
        else{
            digitalWrite(STEP_PIN, LOW);
            leadingEdge = true;
        }
    }
    OCR1A = minInteruptsPerStep + (minInteruptsPerStep * (1 - motorSpeed));
}

В loop() У меня есть функция, которая проверяет состояние motorSpeed для управления другими функциями

 if (motorSpeed == targetSpeed) {
    motorState = RUNNING;
} else if (motorSpeed < targetSpeed) {
    motorState  = ACCELERATING;
} else if (motorSpeed > targetSpeed) {
    motorState  = DECELERATING;
}

motorSpeed и targetSpeed являются поплавками и варьируется от 0-1, с шагом 0,1 speedIncrement равно 0.001. Насколько я могу видеть, это должно ускорить скорость до motorSpeed == targetSpeed, на 100 шагов. И в основном это происходит. Скорость доходит до targetSpeed и motorState переключается с ACCELERATING to RUNNING, как и ожидалось .. в основном. Проблема в том, что она иногда начинает перелистывать между тремя состояниями, без всякой причины, я могу видеть - нет ничего, что должно изменить значение motorSpeed за пределами обработчика прерываний, поэтому, как только он прекращает ускорение, он должен быть стабильным.

Это потому, что я использую и сравниваю поплавки? Должен ли я масштабировать переменные скорости вверх и использовать int или даже байт?

2 голоса | спросил stib 15 PM00000020000003031 2018, 14:13:30

3 ответа


1

Самая большая проблема здесь в том, что поплавки плохо сравниваются. Проблема в том, что float не является точным числом. Это в значительной степени приближение.

Некоторые числа не могут быть представлены поплавками, поэтому ваш «100» может быть фактически «100.00001» или «99.999999».

Лучше использовать целые числа, когда это возможно. Сделайте свои расчеты с использованием больших чисел, а затем уменьшите результаты, когда вам нужно их использовать.

Если вы работаете с шагом 0,1 между 0 и 100, вы должны действительно работать с шагом 1 между 0 и 1000, а затем делить на 10 позже, когда вам нужно использовать значение (если вы даже действительно нужно это сделать).

Вот небольшой пример, который я использовал в Linux:

#include <stdio.h>

void main() {
    float f = 0;
    while (f < 100) {
        f += 0.1;
    }
    printf("%.6f\n",f);
}

Вы ожидали, что он ответит «100.000000». Это не так. Вместо этого вы получаете 100.099045. Это потому, что наиболее близким к 100 является фактически предыдущая итерация: 99.999046.

Если вы хотите сравнить поплавки, лучший способ - использовать операцию «внутри X». Вот небольшой макрос:

#define WITHIN(A,B,DIFF) (fabs((A) - (B)) <= (DIFF))

Затем вы можете:

if (WITHIN(motorSpeed, targetSpeed, 0.1)) { 
    ...
}

Это должно дать вам ИСТИННЫЙ, если motorSpeed ​​и targetSpeed ​​находятся в пределах 0,1 друг от друга.

ответил Majenko 15 PM00000020000004331 2018, 14:31:43
3

Вы уже знаете, из предыдущих ответов, почему сравнение не дает вы ожидаете результат: вы предположили, что любое кратное 0,1 также кратное 0,001, что верно для действительных чисел, но не для плавающих.

Чтобы устранить проблему, вам было предложено разрешить некоторые суетливость в сравнении. Я бы предложил другой подход: держать ваши сравнения, как они есть, и вместо этого изменить способ скорости обновлено в ISR:

 if (motorSpeed < targetSpeed) {
    motorSpeed = min(motorSpeed + speedIncrement, targetSpeed);
}
else if (motorSpeed > targetSpeed) {
    motorSpeed = max(motorSpeed - speedIncrement, targetSpeed);
}

С этой логикой motorSpeed всегда будет ровно равным на targetSpeed, независимо от того, является ли изменение скорости множественным от приращения или нет.

Распространенное заблуждение относительно чисел с плавающей запятой имеет то, что они не точный. Правда, большинство вычислений с плавающей запятой включают округление ошибки, но сами числа с плавающей запятой - точные числа , если не числа, которые вы ожидаете¹. Затем, когда вы выполняете задание

 motorSpeed = targetSpeed;

, который в какой-то момент заканчивается выше, вы можете протестировать

 if (motorSpeed == targetSpeed) ...

, и это будет правда.

¹ Когда вы пишете «0,1» в исходном коде, вы получаете код float, который ближайший к 0,1, который составляет 0,100000001490116119384765625 точно .

ответил Edgar Bonet 16 AM000000120000004431 2018, 00:11:44
1

Вы не должны сравнивать два поплавка с == или! = напрямую. Причина в том, что float неточно, поэтому, возможно, 1.0 может быть 0.99999999999, а другое значение 1.0 может быть 1.00000000001 (упрощение).

Вместо этого используйте «диапазон», например:

if (fabs(motorSpeed - targetSpeed) < 0.001)
{
   // Equal
}

Это означает, что разница должна быть меньше 0,001. fabs означает абсолютное значение поплавка (математический символ | x | для абсолютного значения).

Если вы сделаете больше сравнений таким образом, сделайте 0.001 a #define или const.

ответил Michel Keijzers 15 PM00000020000001531 2018, 14:30:15

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132