Почему простейший код по разному считает на 2560 и 328p?

Чечако
Offline
Зарегистрирован: 15.06.2018

Господа, у меня похоже приступ кретинизма. Есть программа, отлаженная и работающая на Mega2560.

Сделал версию для 328p (ProMini 5v 16Mhz, если это имеет значение). Код рассчета НЕИЗМЕННЫЙ, это важно. Входящие данные - идентичные. Задача кода - разложить входящую переменную float на символы для отображения на семисегментном индикаторе через max7219.

 

Сам код:

    //положительное число до 99, один знак после запятой.
    pp_val=p_val*10;
    //4 сегмент
    lc.setRow(disp,value_z[0],0x0);
    //3 сегмент
    if(pp_val/100 == 0) lc.setRow(disp,value_z[1],0x0);else lc.setDigit(disp,value_z[1],((pp_val/100)%10),false);
    //2 сегмент
    lc.setDigit(disp,value_z[2],((pp_val/10)%10),true);
    //1 сегмент            
    lc.setDigit(disp,value_z[3],(pp_val%10),false); 

pp_val - временная переменная типа long;

p_val - входящее значение типа float;

Столкнулся я с тем, что имея на входе p_val=0.40, на mega мне индикатор показывает 0.4, как и должен, а на 328p - 0.3

Проблема явно в разложении на разряды, потому как вот такой вот код (отображение с 2 знаками после запятой):

// число от -9 до 9, два знака после запятой. 
    if(p_val<0) 
    {
     minus_sg=1;
     pp_val=p_val*100;
     pp_val=~pp_val+1;
    }
    else
    {
     pp_val=p_val*100;  
    }

    if((pp_val/1000) == 0)
    {
     if(minus_sg) lc.setChar(disp,value_z[0],'-',false); else lc.setChar(disp,value_z[0],' ',false);         // 4 сегмент
    }
    else lc.setDigit(disp,value_z[0],((pp_val/1000)%10),false);
   
    lc.setDigit(disp,value_z[1],((pp_val/100)%10),true);                     // 3 сегмент
    lc.setDigit(disp,value_z[2],((pp_val/10)%10),false);                     // 2 сегмент
    lc.setDigit(disp,value_z[3],(pp_val%10),false);                          // 1 сегмент    

На 328p выводит 0.39 при входящей опять же в 0.40, что подтверждает разную математику. Косяк вылезает на делении, но почему-то только на промини, на меге все корректно. Mega на этом же коде отображает 0.40, как и должна.

Код идентичен, данные на входе идентичны - оба контроллера получают эти 0.40 из одного источника, я проверил. Чуть не свихнулся, впихивая отдадочную печать везде, пока не дошел до функции отображения.

Похоже что округляют при делении они по разному. Есть мысли, как это исправить, сохранив общий код? И вообще, почему оно так происходит?

 

Чечако
Offline
Зарегистрирован: 15.06.2018

Прошу перенести тему в Программирование, если можно - ошибся. :(

Чечако
Offline
Зарегистрирован: 15.06.2018

Копания с отладкой дальше показали, что корень зла первого кода вот в этой строчке:

pp_val=p_val*10;

ПроМини умножает 0.40 на 10, и получает на выходе 3. :-/

Цитата из Serial:

p_val:0.40;
pp_val:3;
p_val:0.40;
pp_val:3;
 
Во втором коде аналогично - после умножения на 0.40 на 100 она получает 39
 
p_val:0.40;
pp_val:39;
p_val:0.40;
pp_val:39;
 
 

Вот КАК?

 
sadman41
Offline
Зарегистрирован: 19.10.2016

Т.е. вот (примерно) так - на разных мегах разные nFloat10?

  float nFloat, nFloat10;
  Serial.begin(115200);
  nFloat = 0.40;
  nFloat10 = nFloat * 10;
  Serial.print("[*01] Float value: "); Serial.println(nFloat, 4);
  Serial.print("[*10] Float value: "); Serial.println(nFloat10, 4);

 

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

Уважаемый, а давайте сначала проведем умножение так float pp_val_float=p_val*10.; а затем проведем ее преобразование pp_val=(long) pp_val_float; Где-то здесь собака порылась...

Чечако
Offline
Зарегистрирован: 15.06.2018

sadman41 пишет:

Т.е. вот (примерно) так - на разных мегах разные nFloat10?


 

Ваш код работает корректно:

p_val:0.40;

pp_val:3;
[*01] Float value: 0.4000
[*10] Float value: 4.0000
[*01] Float value: 0.4000
[*10] Float value: 4.0000
 
Я подозреваю, корень зла в том, что я перемножаю float на long, и возможно что-то еще сказывается. Вымораживает меня то, чтот на mega в 30 сантиметрах все работает как надо. Оба контроллера работают одновременно и обрабатывают одни и теже входящие данные.
 
Чечако
Offline
Зарегистрирован: 15.06.2018

mykaida пишет:

Уважаемый, а давайте сначала проведем умножение так float pp_val_float=p_val*10.; а затем проведем ее преобразование pp_val=(long) pp_val_float; Где-то здесь собака порылась...

   pp_val_float=p_val*10.00;
   pp_val=(long) pp_val_float;

//что так

   pp_val_float=p_val*10;
   pp_val=(long) pp_val_float;

Нуль эмоций:

p_val:0.40;
pp_val:3;
p_val:0.40;
pp_val:3;
 
Но я тоже считаю, что проблема в преобразовании типов.
 
Вот вывод в монитор порта всех трех переменных:
 
p_val:0.40;
pp_val_float:4.00;
pp_val:3;
p_val:0.40;
pp_val_float:4.00;
pp_val:3;
 
Замена long на int ничего не меняет, я попробовал:
p_val:0.40;
pp_val_float:4.00;
pp_val:3;
pi_val:3;
 

Я не знаю, имеет значение или нет - мой код находится внутри функции, при этом p_val - входящий параметр типа float, передающийся в функцию в качестве одного из параметров, а pp_val объявляется внутри функции.

 

Update: 

А вот пример умножения на 100:

 

p_val:0.40;
pp_val_float:40.00;
pp_val:39;
 
mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

Мда, ну тогда только round().

Чечако
Offline
Зарегистрирован: 15.06.2018

mykaida пишет:

Мда, ну тогда только round().

С ним работает корректно, да:

 

   pp_val_float=p_val*10;
   pp_val=round(pp_val_float);

 

Результат:

p_val:0.40;
pp_val_float:4.00;
pp_val:4;
 
Но на Меге-то почему работает нормально? Arduino IDE одно и тоже, 1.8.5. Подключены к одному компу, шьются разве что по разному - мега через встроенный порт с CH340 (robotdyn'овская встраемваемая), Про мини (тоже robotdyn) через CP2102 
 
А так-то спасибо, костыль найден. Но вопрос "какого х.. эээ... почему" по прежнему актуален. :)
b707
Offline
Зарегистрирован: 26.05.2017

Чечако, ничего удивительного.

Тип float - это не точные цифры, а приблизительные. Float 0.4 во внутреннем представлении легко может быть и 0.400001 и 0.39999. В одном случае при целочисленном умножении вы получите 0.4, а в другом 0.3

Чечако
Offline
Зарегистрирован: 15.06.2018

b707 пишет:

Чечако, ничего удивительного.

Тип float - это не точные цифры, а приблизительные. Float 0.4 во внутреннем представлении легко может быть и 0.400001 и 0.39999. В одном случае при целочисленном умножении вы получите 0.4, а в другом 0.3

Почему тогда на меге я все утро вижу 0.4, а на 328p 0.3? Был бы рандом, оно бы проскакивало и там и там. А оно стабильно, как минимум на уровне нескольких минут по логам и нескольких часов по глазам. :)

Ну и главный вопрос - использование round везде в коде исправит проблему, или тоже способно неприятно удивить?

nik182
Offline
Зарегистрирован: 04.05.2015

Вытащено из интернета, но сам всегда делаю так же:

Из книги Дейтелов:
Функция floor может использоваться для округления значения до 
определенного числа знаков дробной части. Оператор 
у = floor( х * 10 + .5 ) / 10; 
округляет х с точностью до одной десятой (первая позиция справа от десятичной 
точки). Оператор 
у = floor( х * 100 + .5 )/ 100; 
округляет х с точностью до одной сотой (вторая позиция справа от десятичной 
точки). 
 
Функция floor находится в библиотеке cmath.
b707
Offline
Зарегистрирован: 26.05.2017

Чечако пишет:

Почему тогда на меге я все утро вижу 0.4, а на 328p 0.3? Был бы рандом

разве я где-то написал, что там рандом? Я говорил, что внутреннее представление числа не точное, но эта "неточность" в заданных условиях всегда одинакова, никакого рандома нет. Конкретное значение будет зависеть от представления числа в регистрах данного процессора. Поэтому на одной атмеге вы всегда получаете 0.3, а на другой 0.4

Чечако
Offline
Зарегистрирован: 15.06.2018

b707 пишет:

разве я где-то написал, что там рандом? Я говорил, что внутреннее представление числа не точное, но эта "неточность" в заданных условиях всегда одинакова, никакого рандома нет. Конкретное значение будет зависеть от представления числа в регистрах данного процессора. Поэтому на одной атмеге вы всегда получаете 0.3, а на другой 0.4

Понял, спасибо. А эта "неточность" - она является характеристикой всей серии микроконтроллеров, или каждого конкретного чипа в ней?

Т.е. скажем получать 0.4 я буду на любой 2560 и 0.3 на любой 328p, или каждый чип надо смотреть индивидуально?

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

В принципе тут можно сделать и еще один вывод - к преобразованию типов переменных стоит относится с бо..ольшой осторожностью!

b707
Offline
Зарегистрирован: 26.05.2017

Чечако пишет:

Понял, спасибо. А эта "неточность" - она является характеристикой всей серии микроконтроллеров, или каждого конкретного чипа в ней?

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Чечако пишет:

Понял, спасибо. А эта "неточность" - она является характеристикой всей серии микроконтроллеров, или каждого конкретного чипа в ней?

В данном случае "неточность" - качество, присущее используемое Вами способу вычисления.

При округлении следует перед операцией прибавлять половину единицы младшего разряда. В данном случае это 0.005.

Чечако
Offline
Зарегистрирован: 15.06.2018

andriano пишет:

В данном случае "неточность" - качество, присущее используемое Вами способу вычисления.

При округлении следует перед операцией прибавлять половину единицы младшего разряда. В данном случае это 0.005.

Этот вариант я знаю, да. Но меня смутило именно то, что вынесено в заголовок - что код корректно работает на 2560, и не работает на 328p. :) Я исходил из того, что подобные операции или  работают корректно на всех arduino, или опять же не работают на всех. Избирательный характер мне в голову не пришел. :(

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Видите ли, когда в программе есть ошибка, то (в среднем) трудно предсказать заранее, в каких случаях она проявится.

В пнринципе, Вы выяснили, что для 328 и 2560 используется немного отличающаяся математика. Почему? Где? Зачем? И вообще - умышленно это сделано иои случайно так оказалось, можно, конечно, выяснить, покопавшись в исходниках. Вы готовы потратить на это свое время?

Можно найти массу подобных вопросов. Например, ЗАЧЕМ char сделан знаковым для 8-разрядных контроллеров и беззнаковым - для 32-разрядных? Стандарт Си допускает и то и другое, но зачем в разных моделях Ардуино этот вопрос решен по-разному?

В конце концов, писать программы надо так, чтобы подобные различия не сказывались на результатах работы программы, а все остальное - пустое любопытство.