NOVINKA - Online rekvalifikační kurz Python programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Diskuze: Laserová hračka pro kočku - Zaokrouhlení float vrací nesmysl

V předchozím kvízu, Online test znalostí C++, jsme si ověřili nabyté zkušenosti z kurzu.

Aktivity
Avatar
Caster
Člen
Avatar
Caster:12.12.2022 23:31

Podle projektu Arduino Cat laser toy DIY programuji pro kočku laserové ukazovátko, které bude laserem simulovat pohyb mouchy na podlaze ;-)

Zkusil jsem: Vlastní program jsem upravil pro MCU ATtiny202 (128 B SRAM, 2 KB Flash) ale než ho pustím na dvě serva, ověřuji jeho funkčnost v C++ VS2022.

Na dvou řádcích x_position = round(x_position + x_speed); jsem doplnil funkci zaokrouhlení. Bez toho mi zkušební printf vypsal min. a max. hodnoty:
4.980209 49.999805 4.996557 35.027714
Čísla chci zaokrouhlit na 5 50 5 35 tak abych u 8-bit MCU mohl použít uint8_t

Testovací program C++ VS2022:

#include <iostream>

#include <chrono>
#include <thread>

#include <random>
using namespace std;

/* YOU CAN CUSTOM THESE VARIABLES IF YOU WANT TO ALTER THE TOWER BEHAVIOUR */

// X servo angle will stay in [min_x, max_x] range
// Y servo angle will stay in [min_y, max_y] range
// to be ajsuted to the size of your living room

float x_min = 10, x_max = 0;
float y_min = 10, y_max = 0;

float min_x = 5;
float max_x = 50;
float min_y = 5;
float max_y = 35;
int min_freeze = 600;
int max_freeze = 3000;
float minimal_movement = 5;
int LaserValue = 180; // 0 =0V and 255 =5V lower the value if you want to dim the laser, higher it if you want to brighten it, higher the value to max 255
/* YOU SHOULD NOT HAVE TO MODIFY THE CODE BELOW THIS LINE */

// finding center of square for starting point
int random_delay;
float x_position = min_x + (max_x - min_x) / 2;
float y_position = min_y + (max_y - min_y) / 2;
float x_old_position = x_position;
float y_old_position = y_position;
float x_new_position;
float y_new_position;
float x_speed;
float y_speed;
int movement_time;

random_device rd;   // non-deterministic generator
mt19937 gen(rd());  // to seed mersenne twister.
uniform_int_distribution<> dist1040(10, 40);
uniform_int_distribution<> distFreeze(min_freeze, max_freeze);
uniform_int_distribution<> distX(min_x + minimal_movement, max_x - minimal_movement);
uniform_int_distribution<> distY(min_y + minimal_movement, max_y - minimal_movement);

// Instantiating two servos
//Servo x_servo;
//Servo y_servo;
int pos = 0;

void setup() {
    //y_servo.attach(6);  // attaches the y servo on pin 6 to the servo object
    //x_servo.attach(9);  // attaches the x servo on pin 9 to the servo object

    //pinMode(3, OUTPUT);
    //analogWrite(3, LaserValue);  // switch on  the laser

    //Place the servos in the center at the beginning
    //y_servo.write(y_position);
    //x_servo.write(x_position);

}

int main()
{
        for (uint32_t i = 1000000; i > 0; i--) {
                movement_time = dist1040(gen);
                random_delay = distFreeze(gen);
                x_new_position = distX(gen);
                y_new_position = distY(gen);

                if ((y_new_position > y_old_position) && (abs(y_new_position - y_old_position) < 5)) {
                        y_new_position = y_new_position + minimal_movement;
                }
                else if ((y_new_position < y_old_position) && (abs(y_new_position - y_old_position) < 5)) {
                        y_new_position = y_new_position - minimal_movement;
                }

                if ((x_new_position > x_old_position) && (abs(x_new_position - x_old_position) < 5)) {
                        x_new_position = x_new_position + minimal_movement;
                }
                else if ((x_new_position < x_old_position) && (abs(x_new_position - x_old_position) < 5)) {
                        x_new_position = x_new_position - minimal_movement;
                }

                //if (x_new_position > x_max) { x_max = x_new_position; }
                //if (x_new_position < x_min) { x_min = x_new_position; }
                //if (y_new_position > y_max) { y_max = y_new_position; }
                //if (y_new_position < y_min) { y_min = y_new_position; }

                x_speed = (x_new_position - x_old_position) / movement_time;
                y_speed = (y_new_position - y_old_position) / movement_time;
                for (pos = 0; pos < movement_time; pos += 1) {
                        x_position = round(x_position + x_speed);
                        y_position = round(y_position + y_speed);
                        if (x_position > x_max) { x_max = x_position; }
                        if (x_position < x_min) { x_min = x_position; }
                        if (y_position > y_max) { y_max = y_position; }
                        if (y_position < y_min) { y_min = y_position; }
                        //printf("%f, %f\r", x_position, y_position);

                        //x_servo.write(x_position);
                        //y_servo.write(y_position);
                        //delay(10);
                }
                x_old_position = x_new_position;
                y_old_position = y_new_position;
                //std::this_thread::sleep_for(std::chrono::milliseconds(random_delay));
        }
        printf("%f %f %f %f\n", x_min, x_max, y_min, y_max);
}

Chci docílit: Po přidání funkce round jsou výsledkem nesmyslné hodnoty:
-316611.000000 40.000000 -294489.000000 112.000000

Při vložení funkce round() nabízí VS2022 asi 8 možností jejích různých verzí podle typu proměnné, použil jsem inline float round(float _Xx) +3 přetížení. V diskuzi na stackoverlow píší že C++ nemá funkci zaokrouhlení nebo je chybně implementována.

Typ funkce round() použitý v programu

Generování PWM pomocí ATtiny202 pro řízení dvou serv. Vlastní řízení bude v rozsahu pulzu (+Width) 1-2 ms (-90° až +90°).

 
Odpovědět
12.12.2022 23:31
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Caster
DarkCoder:13.12.2022 1:17

A proč si tu funkci jednoduše nenapíšeš? :-)

int my_round(double d) {
    return (d > 0) ? (int)(d + 0.5) : (int)(d - 0.5);
}
Nahoru Odpovědět
13.12.2022 1:17
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Caster
Člen
Avatar
Caster:13.12.2022 8:49

Díky, to mě napadlo také. Jde mi ale o to, proč nefunguje funkce zaokrouhlení v C++ pro float.

 
Nahoru Odpovědět
13.12.2022 8:49
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Caster
DarkCoder:13.12.2022 10:32

Vše funguje normálně.

#include <iostream>
using namespace std;

int main() {
    cout << (int)round(4.980209f) << '\n'; // 5
    cout << (int)round(49.999805f) << '\n'; // 50
    cout << (int)round(4.996557f) << '\n'; // 5
    cout << (int)round(35.027714f) << '\n'; // 35

    float val1 = 4.980209f;
    float val2 = 49.999805f;
    float val3 = 4.996557f;
    float val4 = 35.027714f;

    cout << (int)round(val1) << '\n'; // 5
    cout << (int)round(val2) << '\n'; // 50
    cout << (int)round(val3) << '\n'; // 5
    cout << (int)round(val4) << '\n'; // 35

    cout << (int)nearbyint(val1) << '\n'; // 5
    cout << (int)nearbyint(val2) << '\n'; // 50
    cout << (int)nearbyint(val3) << '\n'; // 5
    cout << (int)nearbyint(val4) << '\n'; // 35

    cout << (int)rint(val1) << '\n'; // 5
    cout << (int)rint(val2) << '\n'; // 50
    cout << (int)rint(val3) << '\n'; // 5
    cout << (int)rint(val4) << '\n'; // 35

    cout << lrint(val1) << '\n'; // 5 long
    cout << lrint(val2) << '\n'; // 50 long
    cout << lrint(val3) << '\n'; // 5 long
    cout << lrint(val4) << '\n'; // 35 long

    return 0;
}

Ve všech případech se volají funkce s parametrem typu float.

Nahoru Odpovědět
13.12.2022 10:32
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Caster
Člen
Avatar
Odpovídá na DarkCoder
Caster:13.12.2022 14:31

Proč nestačí použít round() bez přetypování na int ? Nesmysly to počítá právě když použiji jen round() bez přetypování. Hodnoty pro zaokrouhlení jsou float.

 
Nahoru Odpovědět
13.12.2022 14:31
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Caster
DarkCoder:13.12.2022 15:29

Funkce round() vrací desetinné typy v závislosti na typu parametru přetížené funkce která se zrovna volá.

Pro vrácení celého čísla je tak třeba přetypovat na int, čímž se ořízne desetinná část.

Velikost datového typu float a int je stejná. Pohled na typ je však odlišný.

Další přetížení pro celá čísla se získají vložením cmath.

Nahoru Odpovědět
13.12.2022 15:29
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Caster
Člen
Avatar
Caster:13.12.2022 18:29

Vůbec nechápu smysl přetěžování, je to podle mě nesmysl. Funkce má prostě vrátit požadovanou funkci bez ohledu na typ proměnné.

V programu, který jsem zde uvedl to stále vypisuje nesmysly (výpis min. a max. hodnot x/y_position):

x_position = x_position + x_speed;
y_position = y_position + y_speed;

4.972349 50.006996 4.999432 35.022030 správně

x_position = (int)round(x_position + x_speed);
y_position = (int)round(y_position + y_speed);

-13.000000 315832.000000 -297465.000000 125.000000 chybně

Přesný výsledek je
5 50 5 35 viz definování rozsahu proměnných na začátku programu

 
Nahoru Odpovědět
13.12.2022 18:29
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Caster
DarkCoder:13.12.2022 19:30

Smyslem přetěžování je ulehčit programátorské práci v tom, že si nemusí pamatovat vetsi množství identifikátorů, pod kterými se skrývají odlišné implementace. V C++ to je ještě snesitelné neboť oproti C má vyšší typickou kontrolu. Pro mě však je lepší když mi překladač vyhodí varování než ticho po pěšině..

Na první pohled se může jednat o silnou zbraň, která se ale velmi snadno může změnit v past. A to v podobě toho, zadá-li se odlišný typ, pak funkce provede něco jiného a překladač tuto chybu nezachytí protože přece existuje přetížená funkce.

Nahoru Odpovědět
13.12.2022 19:30
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Petan
Člen
Avatar
Odpovídá na Caster
Petan:14.12.2022 10:59

Proc sy myslis ze jsou nesmyslne ?

if (x_position > x_max) { x_max = x_position; }
if (x_position < x_min) { x_min = x_position; }
if (y_position > y_max) { y_max = y_position; }
if (y_position < y_min) { y_min = y_position; }

tyto hodnoty měníš v průběhu programu
asi by to přirazeni mělo byt naopak

 
Nahoru Odpovědět
14.12.2022 10:59
Avatar
Caster
Člen
Avatar
Odpovídá na Petan
Caster:14.12.2022 14:05

Myslím, že je test v pořádku. Chci tím prověřit, jaký rozsah hodnot x_position a y_position vygeneruje, abych si ověřil, že nepošlu na servo nějakou nesmyslnou hodnotu.

Bez zaokrouhlování program vypíše očekávaný výsledek 4.972349 50.006996 4.999432 35.022030:
viz

float min_x = 5;
float max_x = 50;
float min_y = 5;
float max_y = 35;
 
Nahoru Odpovědět
14.12.2022 14:05
Avatar
Petan
Člen
Avatar
Petan:14.12.2022 22:33

Vysledky nejsou nesmyslne, ale naprosto vporadku, akurat nejsou ty ktere chces, To zaokrouhlovani tam proste nepatri.
Proc ho tam mas ? to zaokrouhleni, tim ze to zaokrouhluje na cele cislo tak se to rozjizdi vuci originalu pri tech 1000000 * movement_time zaznamech

 
Nahoru Odpovědět
14.12.2022 22:33
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 11 zpráv z 11.