странное значение математических операций

Zveruga
Offline
Зарегистрирован: 29.09.2011

Всем привет. Наткнулся на странные грабли, уже голову сломал, может кто сталкивался или может объяснить почему вот такой простой скетч неправильно выполняет простые математические операции:

void setup(){
    Serial.begin(9600);
    byte second, minute, hour;
    hour=12;
    minute=58;
    second=55;
    unsigned long secondCounter=hour*3600+minute*60+second;
    Serial.print("secondCounter ");
    Serial.println(secondCounter);
}
void loop(){
}

на консоль выводится secondCounter 4294948495
если же убрать в объявлении переменной long unsigned, то есть написать 
long secondCounter=hour*3600+minute*60+second;

то выведет вообще отрицательное число: secondCounter -18801

 

Zveruga
Offline
Зарегистрирован: 29.09.2011

Проблему решил, хотя почему компилятор так странно себя ведет все же не понял. Решается проблема добавлением префикса L к целочисленной константе 3600, т.е. запись должна выглядеть вот так:
unsigned long secondCounter=hour*3600L+minute*60+second;
иначе компилятор эти 3600 как-то хитро обрезает и получается неправильный результат. Это странно, т.к. в мануале написано, что "По умолчанию целочисленные константы относятся компилятором к типу int." и соответственно "int занимает 2 байта памяти, и может хранить числа от -32 768 до 32 767 (от -2^15 до 2^15-1)" а 3600 свободно попадает в этот диапазон.

P.S. странный эффект заметил, можно долго биться над проблемой, но после того как напишешь сообщение о проблеме на форуме часто очень быстро сам находишь решение, иногда даже оно находится на этапе описания проблемы.

maksim
Offline
Зарегистрирован: 12.02.2012

А попробуйте вот так:

void setup(){
    Serial.begin(9600);
    unsigned int second, minute, hour;
    hour=12;
    minute=58;
    second=55;
    unsigned long secondCounter=hour*3600+minute*60+second;
    Serial.print("secondCounter ");
    Serial.println(secondCounter);
}
void loop(){
}

 

или так:

void setup(){
    Serial.begin(9600);
    long second, minute, hour;
    hour=12;
    minute=58;
    second=55;
    unsigned long secondCounter=hour*3600+minute*60+second;
    Serial.print("secondCounter ");
    Serial.println(secondCounter);
}
void loop(){
}

 

chervyachok
chervyachok аватар
Offline
Зарегистрирован: 22.12.2011

 Еще проблема решается так

void setup(){
    Serial.begin(9600);
    uint8_t second, minute, hour;
    uint16_t hourSec, minuteSec;
    hour=12;
    minute=58;
    second=55;
    hourSec=3600;
    minuteSec =60;
    uint32_t secondCounter=(hour*hourSec) + (minute*minuteSec) + second;
    Serial.print("secondCounter ");
    Serial.println(secondCounter);
}
chervyachok
chervyachok аватар
Offline
Зарегистрирован: 22.12.2011

maksim пишет:

А попробуйте вот так:

    unsigned int second, minute, hour;
или так:
    long second, minute, hour;
  

У вас получается использование лишней памяти. Если точно известно какое максимальное значение должна принять переменная то зачем выделять ей больше. Естественно все выше сказаное касается случая когда каждый байт на счету. Хотя лучше сразу учится правильно писать.

maksim
Offline
Зарегистрирован: 12.02.2012

long - да это лишнее.

А unsigned int это и есть uint16_t

 

chervyachok
chervyachok аватар
Offline
Зарегистрирован: 22.12.2011

maksim пишет:

А unsigned int это и есть uint16_t

а там вполне хватает 8-ми бит если до 255

step962
Offline
Зарегистрирован: 23.05.2011

Zveruga пишет:

Это странно, т.к. в мануале написано, что "По умолчанию целочисленные константы относятся компилятором к типу int." и соответственно "int занимает 2 байта памяти, и может хранить числа от -32 768 до 32 767 (от -2^15 до 2^15-1)" а 3600 свободно попадает в этот диапазон.

Ничего странного. Вычисления производятся с точностью, определяемой типом переменных/констант в правой части выражения (точнее - "самым точным" операндом). Затем происходит преобразование к типу переменной, стоящей в левой части.

Результат действий над переменными/константами типа int тоже относится к типу int, поэтому, умножив 3600 на 12, вы получите не 43 200 (0xA8C0 или 1010100011000000b), а 10432 (0x28C0 или 0010100011000000) и установленный флаг переполнения. Чтобы действия изначально производились над переменными нужного типа, необходимо в выражении разместить хотя бы одну переменную/константу соответствующего типа. Либо long int (присобачив L к константе или определив хотя бы одну из учавствующих в выраженнии переменных с типом long int), либо float (введя умножение на константу 1.0, но тут может появиться погрешность, связанная с ограничениями на длину мантиссы).

PS:

Хотя нет, возможно, что 12*3600 даст в результате -22335 без флага переполнения (если 3600 будет воспринято компилятором как значение типа int). В любом случае  - результат вычисляется с точностью/разрядностью, соответствующей точности/разрядности самого точного/высокоразрядного операнда.

maksim
Offline
Зарегистрирован: 12.02.2012

Если unsigned long secondCounter = hour*3600 то не хватит.

chervyachok
chervyachok аватар
Offline
Зарегистрирован: 22.12.2011

maksim пишет:

то не хватит.

по моему мы зря спорим, может и я не совсем вьехал но мне кажется:

uint8_t second, minute, hour; - 12,58,55

uint16_t hourSec, minuteSec; - 3600, 60. Тут даже minuteSec можно к 8-ми и будет:

uint8_t second, minute, hour, minuteSec; - 12,58,55,60

uint16_t hourSec; - 3600

uint32_t secondCounter; - 46735 Если бы hour в даном случае не превышал 12 (тоесть от 0 до 12 а не 0 - 24) то и даную переменную можно смело отнести к uint16_t. а uint32_t ввести если нужно было бы посчитать общее количество секунд (46735 + 46735 и тд)
 

void setup(){
    Serial.begin(9600);
    uint8_t second, minute, hour, minuteSec = 60;
    uint16_t hourSec = 3600, secondCounter;
    uint32_t totalSec;
    hour=12;   //mах 12
    minute=58; //mах 60
    second=55; //mах 60
    secondCounter=(hour*hourSec) + (minute*minuteSec) + second;
    Serial.println(secondCounter);
    totalSec = totalSec + secondCounter + secondCounter + secondCounter;    
    Serial.println(totalSec);
}

Вот, вполне работоспособно

 

Zveruga
Offline
Зарегистрирован: 29.09.2011

step962 пишет:

Ничего странного. Вычисления производятся с точностью, определяемой типом переменных/констант в правой части выражения (точнее - "самым точным" операндом). Затем происходит преобразование к типу переменной, стоящей в левой части.

Теперь понятно, спасибо.