Сравнение двух float

В общем уже голова не варит, половина четвёртого утра. Надеялся добить, но теперь уже завтра и с Вашей помощью.
Суть такова:

в main вываливаюсь в текущее состояние:
if (state == START_HEATING)   // начало нагрева
            {
                temp_check();
                heatstart();
            }

вызываем функции опроса датчика и фиксации текущей температуры в переменной degrees

void temp_check(void)
  {
     m_wait_ds20 = 0;    
     if(m_ctr - m_wait_ds20 >= 1500)   
         {
          ds18b20_init(0,1,100,DS18B20_12BIT_RES);  
          m_wait_ds20 = m_ctr;     
          degrees = ds18b20_temperature(0);
         }

и включения нагрева

void heatstart (void)
   {
        if (starttemp < degrees)
        heater_1_on;
        
         if (starttemp >= degrees)
        {
            heater_1_off;
            state = HEATING_OFF;
         }
   }  


соответственно degrees объявлена как float. starttemp, тоже float
float degrees, starttemp;


starttemp задаётся с кнопки в отдельной функции во время настройки
void setuptemp (void)
   {
     lcd_gotoxy(0,0);
     lcd_putsf("Set temperature:");
     if(plus_pressed)
    	{
    	starttemp += 1;
        if (starttemp >= 100)
        starttemp = 20;
        sprintf(lcd_buf, "%.1f\xdfC", starttemp);
    	lcd_gotoxy(0,1);
        lcd_puts(lcd_buf);
        }  
    
    if (start_pressed)
    state = STARTPROCESS;
    
   }


Сука, сто процентов выполняется условие
if (starttemp < degrees)

а при отладке перескакивает на
if (starttemp >= degrees)
        {
            heater_1_off;
            state = HEATING_OFF;
         }


Вот ведь не 100%!!!

А всё с точностью до наоборот!
if (starttemp > degrees)

Ну конечно же, заданная больше текущей должна быть, чтобы нагрев включился!
Ночная жесть. Два часа над этой лабудой бился, а если бы лёг спать два часа назад, то с утра за 10 минут нашёл бы косяк! Я уже все талмуды про кошерное сравнение двух float прочитал, и каждый дефайн проверил, и все контакты тестером прошунтировал, дабы убедиться, что не в железе дело!
Мораль: не пренебрегайте здоровым и крепким сном!

Комментарии (62)

RSS свернуть / развернуть
И к чему этот высер?
0
Наверное к тому, чтоб ты сюда зашел и насрал. Спецом для тебя старался, чтобы тебе с утра было где облегчиться.
-2
Вениамин, не старайтесь выглядеть бОльшим мудаком, чем вы есть.
-2
Можете не беспокоиться по этому поводу, до Вашего третьего места на пьедестале мудаков данного ресурса мне никогда не добраться, как бы я ни старался.
0
А кто занимает почетные первые места?
0
А зачем там вообще float? Даллас всё равно даёт точность в 1/4 градуса. Сделайте просто хранение температуры в градусах*100. Это сэкономит кучу времени при исполнении и размер кода будет меньше. Сравнение флоатов небезопасно, особенно если это данные с датчика без проверки на валидность.
+3
Присоединяюсь.
+1
Соглашусь! Я бы пилил целочисленной математикой все… меньше потом гемору с переводами да обсчетами
+1
А это я не в курсе, не задумывался о том, что с датчика можно показания забирать не во float-переменную. Пока всё работает, но когда возьму напильник в руки — данное замечание будет в приоритете.
0
А оно и будет прекрасно работать какое-то время. Пока в один день термостат не начнёт подогревать сусло до NaN градусов.
0
буду исправлять значит.
0
Мораль: не пренебрегайте здоровым и крепким сном!
А еще — внятными названиями переменных.
+2
  • avatar
  • Vga
  • 13 февраля 2016, 13:39
А в чём невнятность моих переменных? Я просто брал название процесса или операции на английском, m_ctr — счётчик миллисекунд, m_wait_ds — время ожидания для датчика, но тут можно поработать, так как я эту переменную потом ещё в нескольких местах использовал. Остальное вроде бы всё понятно.
0
А у тебя ошибка и не в них, а в starttemp и degrees, которые названы бессмысленно. Заметить ошибку в строке settemp < curtemp проще.

Я бы еще покритиковал программирование «в стиле бейсика». Процедуры без параметров и глобальные переменные.
+2
Добавлю от себя, помимо того, что написал Vga . После того как написали программу — попробуйте взглянуть на нее со стороны, как будто это чужая программа. Например Вы бы поняли (глядя в чужой код), что m_ctr — это счётчик миллисекунд. Я бы никогда не понял смысл названия данной переменной, если бы Вы не сказали.

Плюс, пытайтесь писать в одном стиле. Например, если мы используем знак «_» для разделения составных названий — то давайте всегда придерживаться этого правила. Можно писать set_temp, или settemp, или setTemp, не столь принципиально как именно, главное единообразно, не смешивая разные стили.

Особенно хочется выделить
heater_1_on;

не делайте так, ибо непонятно, что это за конструкция.
+1
не делайте так, ибо непонятно, что это за конструкция.
Я как пасквилянт даже не заметил, что там скобочек нет) Сразу решил, что это процедура (ну, точнее, макрос). В конкретно этом мне больше не нравится то, что неясно, что за «нагреватель 1», да и вообще, я бы передавал номер как аргумент.
0
m_ctr ???

если мы используем знак «_» для разделения составных названий — то давайте всегда придерживаться этого правила.

heater_1_on; ???###!!!

Эдак это будет бесконечный разговор с ним на этом ресурсе — пусть лучше вначале прочитает эту книжку или еще какую подобную.

0
Человек первый раз пишет программу на С. И у него это неплохо получается (если бы он не спросил «А в чём невнятность моих переменных?» — я бы вообще не делал акцент на читабельности). На данном этапе (ИМХО) у него и так все неплохо получается.
пусть лучше вначале прочитает

пусть лучше прочитает Боба Мартина «Чистый код», но не сейчас, а когда сам придет к необходимости «красивого» кода.
+2
ибо непонятно, что это за конструкция
Как так?! Даже индус/китаец поймет, что это heater #1 turn_on, тем более, что код очень легко расширяется копи-пастой при укрупнении СИСТЕМЫ до размеров пивзавода а-ля heater_12_on;...heater_99_on; e.t.c.
-2
расширяется копи-пастой при укрупнении СИСТЕМЫ до размеров пивзавода а-ля

Я Вот не понимаю, че Вас так колбасит? У вас личная неприязнь (или зависть) к VeniaminCaver ?
0
У вас личная неприязнь (или зависть)
Да, я как почитаю его теории, его код здесь — кушить ни магу!
-1
Ну вот и серебряный призёр нашего воображаемого пьедестала пожаловал! Я смотрю метите на первое место?
+1
Так же не пренебрегайте
0
  • avatar
  • Jman
  • 13 февраля 2016, 23:40
пожалели исходники. Из всего написанного невозможно уловить суть. Или здесь как в анекдоте: «Вчера у нас открыли пивнушку… — А в чем суть? — А ссуть в подъезде...»
0
Сделаю, заработает, и выложу полностью. Сейчас пока смысла нет выкладывать, так как сделано не до конца, но я знаю как делать. Лучше я сделаю всё, что смогу, оно заработает, а тогда можно будет выложить и пособирать комментарии относительно моментов, которые я, ну совсем уж через пятую точку сделал, ввиду слабого владения си. Этот кусок я уже опубликовал практически, и тогда узрел в чём проблема, а так как уже в редакторе всё подготовил, то жалко было не публиковать, а найти ошибку очень хотелось, чтобы первый кусок кода на практике опробовать. Видите — даже с такого куска уже указали на косяки, ведь мне и вправду десятые доли градуса в принципе не нужны — хотя на практике может и пригодится, для того, чтобы фиксировать изменения температуры в заторе. Как показывает практика имеется тенденция к слабому увеличению температуры после 20-30 минут покоя, и было бы интересно посмотреть с какой скоростью она увеличивается, когда это увеличение начинается, равномерно ли протекает.
0
if (state == START_HEATING)

хинт: поменять местами state и START_HEATING
От перемены суть не изменится, а вот одними граблями меньше
0
Йоды сравнение называется это.
+1
Интересное замечание, даже не задумывался над этим, а так по-видимому и вправду будет проще воспринимать код. Благодарю.
0
Не. Грабли случаются, если вместо == случайно вбить =
0
В моем случае компилятор выдаст ошибку, а в первоначальном — грабли.
0
Это из цикла «техника безопасности при написании исходного кода»
0
Извращение это. А на опечатку компилятор укажет варнингом.
0
А то их кто-то читает. Компиляторы, опять же, разные бывают. Некоторые молча компилируют. А сравнение Йоды не скомпилируется гарантированно.
0
Спорно. Нотация Йоды не решает всех проблем, а вот читаемость ИМХО падает. Касательно варнингов и «а кто их читает» — я бы вообще рекомендовал всем начинающим использовать режим, когда варнинги считаются ошибкой (-Werror в GCC).
+1
я бы вообще рекомендовал всем начинающим использовать режим, когда варнинги считаются ошибкой (-Werror в GCC).
Это хорошая рекомендация, но проблема как раз в том, что именно новички склонны игнорировать варнинги.
И не все компиляторы выдают такой варнинг. TCC, например, вообще не склонен к внятности и обильности ругани.
Нотация Йоды не решает всех проблем, а вот читаемость ИМХО падает.
Хз-хз. Читабельность на мой взгляд не страдает (быстро привыкнешь к такой нотации, да и ничего по большому счету она не меняет в плане читабельности), а одну распространенную проблему оно таки решает.
0
Сравнения присвоение вместо если, «Варнинг!» компилятор выдаёт.
Я прошиваю только когда варнингов нет, за редким исключением если например переменная заведена а значение для неё не присвоено, и на данном этапе мне это не мешает.
С Йодой не прокатило — мне всё же проще когда в мозгу прокручиваешь «Если Состояние — TRANCEFUSION, то „
+1
Я прошиваю только когда варнингов нет
Похвально.
С Йодой не прокатило — мне всё же проще когда в мозгу прокручиваешь «Если Состояние — TRANCEFUSION, то „
Это вопрос привычки. Недельку следишь за собой и заставляешь писать так, затем оно так пишется уже на автомате.
0
Это как привычка включать поворотники прежде чем двигать рулем.
Независимо от того, что увидел в зеркала.
Просто тупо делаешь и не загружаешь себя лишней информацией.
+2
ещё из личной коллекции граблей — все float константы (дефайны) должны оканчиваться суффиксом f
типа: #define someFloatConst 1234.5678f
0
А разве константа с точкой может распознаться как что-то иное?
0
А разве константа с точкой может распознаться как что-то иное?
Но ведь у Captain Obvious тоже буква O на лбу написана.
0
Константа с точкой (без суффикса) будет иметь тип double. Поэтому иногда имеет смысл ставить суффикс, если константа без потери точности может быть выражена как float.
0
Ага, понятно. А оно всегда будет иметь тип double или зависит от конкретного компилятора?
0
В стандарте четко написано

An unsuffixed floating constant has type double. If suffixed by the letter f or F, it has type float. If suffixed by the letter l or L, it has type long double.
Но нельзя поручится, что абсолютно все компиляторы выполняют требование стандарта.
0
Без f можно получить операции с double, что несколько неприятно если он софтовый а float аппаратный. Просто добавка, не примите за возражение.
+1
а если не с точкой, а с запятой?
Только не говорите, что такое невозможно :)
Неаккуратный «копипейст» из экселя или калькулятора, и вот он — shit, который happens регулярно
0
Насколько я вижу, используется стандартная библиотека для работы с DS18B20 компилятора CodevisionAVR. В ней функция ds18b20_temperature() может вернуть значение -9999 градусов, если не удалось считать температуру из датчика. Определение функции можно посмотреть в файле cvavr2\lib\DS18B20.lib.
Нужно отдельно обработать данное значение, иначе при неисправности датчика нагреватель не выключится никогда.
+1
Спасибо за пояснение, я уже этим пользуюсь. У меня вся конструкция находится в помещении, температуру я меряю от нуля до 100. При старте у меня все данные с датчиков считываются и передаются на обработку. При обработке если температура меньше нуля то автомат вываливается в заглушку с ошибками, с пометкой что это ошибка датчика температуры.
0
если я правильно помню, то числа IEEE 754 сконструированы так, что они монотонны в бинарном представлении, т.е. их можно сравнивать целочисленным сравнением, загрузив в целочисленные регистры.
0
Если так, то по идее компилятор должен быть достаточно умен, чтобы именно так и делать.
0
возможно, компилятор так делает в каких-то случаях, не обращял внимания.
Вобщем, смотрите сами мою магию для примера:

	for(int k = 0; k < 10000; k ++) {
		double phi = M_PI * (double)k * 2.l / 10000.l;
		double x = cos(phi);
		double y = sin(phi);
		int64_t ix= *reinterpret_cast<int64_t*>(&x);
		int64_t iy= *reinterpret_cast<int64_t*>(&y);
		int c = x < y ? 1 : 0;
		int ic = ix < iy ? 1 : 0;
		if (ix & iy & 0x8000000000000000) ic ^= 1;
		//printf("%d%d\t%.12g\t%.12g\n", c, ic, x, y);
		if(c != ic) {
			printf("%.12g\t%.12g\n", x, y);
		}
	}

результаты сравнения как целых и как double совпадают.
0
А Вы попробуйте выполнить такой код:


int main()
{
        static double a = 1.0 / 0.0;
	static double b = 0.0 / 0.0;

	if (a > b) {
		printf("a > b\n");
	}

	int64_t ia = *reinterpret_cast<int64_t *>(&a);
	int64_t ib = *reinterpret_cast<int64_t *>(&b);

	if (ia > ib) {
		printf("ia > ib\n");
	}

}

:)

Да и вообще, подобного рода «магия» даже если и работает в частных случаях, ломает переносимость кода.
0
это особые случаи. одно inf, а другое -NaN, они элементарно распознаются :)
0
Да, это именно специально подобранный случай, в реальном коде сравнение +inf с NaN встречается достаточно редко :)

Но, ИМХО, предложенная Вами оптимизация имеет кучу минусов: размерность double может меняться, на разных платформах может меняться порядок байт (BE/LE), некоторые платформы требуют выравнивания адресации и т.д.

Такого рода «магия» — это очень специфическая оптимизация, и ИМХО нужно 10 раз подумать перед тем как ее использовать.
0
Нужно 10 раз подумать, прежде чем использовать софт-флоаты и отказаться от этой идеи
0
fix: Нужно один раз подумать, прежде чем использовать софт-флоаты и отказаться от этой идеи
0
а вообще, да, фиксед-поинт для обработки значений с датчиков — самое то. и алгоритмов вагон, всё уже придумано.
0
Касательно того, что нужно подумать — я с вами согласен (во многих случаях есть смысл отказаться от чисел с плавающей точкой). Но Вы уж слишком категоричны (как всегда), в ряде случаев использование софт-флоатов вполне оправдано.
0
будучи загруженными в регистры, порядок байт уже не имеет никакого значения (непонятно, почему для многих это непонятно:).
размерность дабла IEEE754 — 64 бит, расположение описано в стандарте, то же верно и для single precision (float, 32бит).
это даже не оптимизация, а пруф оф консепт. Я хотел просто показать, что над стандартом поработали хорошие инженеры, и стандарт позволяет сделать довольно простую аппаратуру / софтовую эмуляцию для работы с действительнвми числами.
0
размерность дабла IEEE754 — 64 бит, расположение описано в стандарте, то же верно и для single precision (float, 32бит).
Да, только иногда за стандарт забивают. Далеко ходить не будем, возьмем GCC для AVR – там double 4 байта.

Касательно BE/LE – важно то как хранятся данные в памяти. Если мы записали в память число через регистр общего назначения, а потом прочитали — то да мы получим тоже число независимо от BE/LE. Но существовали (а может и существуют) архитектуры, в которых целые числа хранились как LE но при этом числа с плавающей точкой как BE. И вот в этом случае мы потенциально получим проблему. Об этом немного написано тут, в разделе Floating-point.
0
не думаю, что сейчас существуют процессоры, где у float — MSB, а у integer — LSB. Они не прошли естественный отбор из-за этого геморроя.

а AVR — 8-битка, там родной int — 2 байта, а double == float, думаю, из-за невозможности вменяемо это реализовать на 8 битах.
Но плавающая точка на 8битках — это какое-то извращение в наши дни.
0
В жопу магию.
В смысле, в функцию MagicShit()
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.