EEPROM и округление чисел

Здравствуйте!

Здесь два мизерных скетча, два варианта округления чисел.

Необходимо в переменные r1 и r2 записывать округленные значения x.

Вариант 1.

void setup()
{
float x=735.123456789; // x нужно округлить до трех знаков
String s=String(x,3);
float r1=s.toFloat();

x=735.123432101;
s=String(x,3);
float r2=s.toFloat();
}

void loop()
{

}
// Скетч использует 4882 байт...
// Глобальные переменные используют 21 байт...

Вариант 2.

void setup()
{
float x=735.123456789;
float r1=trunc(x*1000)/1000;  // округлили

x=735.123432101;
float r2=trunc(x*1000)/1000; 
}

void loop()
{

}
// Скетч использует 444 байт...
// Глобальные переменные используют 9 байт...

Первый вариант гарантирует, что в ячейках памяти r1 и r2 будут ОДИНАКОВЫЕ БИТЫ. Но памяти он занимает много.
И в этом варианте после записи r1 в EEPROM r2 уже не запишится (update).

Вопрос. Будет ли содержимое ячеек r1 и r2 совпадать при таком округлении во втором варианте?
Спасибо!

Не понимаю, к чему в вопросе ЕЕПРОМ. Тем более что в коде никакого ЕЕПРОМА нет.

Если вас интересуют, будут ли значения float r1 и r2 совпадать до бита (зачем?) - просто напечатайте составляющие их байты

ЗЫ Если вам надо хранить и обрабатывать это число с точностью трех знаков после точки - просто умножьте его на 1000 и работайте с целым.

Если r1 уже записано в EEPROM, а r2 отличается от r1 на мизерную величину, то r2 перезапишется в EEPROM. Зачем изнашивать EEPROM лишней записью, если на дисплее я буду видеть одинаковые значения: что r1 округлю, что r2 округлю?

На самом деле есть три варианта округления чисел. А, если мы учитываем и отрицательные числа - еще больше.
Это - если рассматривать только правильные варианты. С неправильными - намного больше.
Вариант с форматным преобразованием (через строку) вряд ли можно считать правильным.

И по поводу приведенного кода.
Я сходу не скажу, что именно делается в первом варианте, но по поводу второго варианта есть сильные подозрения, что вычисления делает компилятор, а не скетч. Т.е. сам скетч вычислений не производит, а потому бессмысленно говорить о его размере.

Возьмите да проверьте - напечатайте каждый байт для значений r1 и r2.

Ставлю на то, что во втором случае они будут одинаковы, но лучше напечатать

За второй случай не знаю, а в первом они будут точно одинаковы, так как s в строках 4 и 8 равны.

По моему, если игнорируете “мизерная разность”, то и сравнивайте разность r1 и r2
Если игнорируете при одинаковом отображении, то сравнивайте отображение r1 и r2 (т.е. отформатированный для вывода String)
Т.к. в общем случае, мизерная разность может привести к разному отображению.

Запустите и покажите что печатает

void printFloatAsHEX(float f) {
 uint32_t x = 0;
memcpy((uint8_t *)&x, (uint8_t*)&f, 4);
Serial.println(x,HEX);
}

void setup()
{
Serial.begin(9600);

float x=735.123456789;
float r1=trunc(x*1000)/1000;  // округлили
printFloatAsHEX(r1);

x=735.123432101;
float r2=trunc(x*1000)/1000; 
printFloatAsHEX(r2);
}
1 лайк

Вопрос возник вот в связи с чем.
Мне надо в цикле с датчика BMP180 получать атмосферное давление и фиксировать его минимум и максимум в EEPROM. В первом цикле я могу увидеть, например, 735.7 мм рт. ст. (после первода дюймов рт. ст. в мм рт. ст. и округления), и во втором цикле увидеть то же самое. Но значения переменной, из которой я получаю давление в первом и втором циклах могут при этом отличатся. При этом может произойти лишняя запись в EEPROM.

Во-первых, проведите тест, который я вам написал в #8

А во-вторых, как я уже указывал выше - лучше вообще избавлятся от флоат и работать с целыми. Если вам нужны десятые в цифре давления - домножьте его на 10, если сотые - на сто.
Полученное целое храните в ЕЕПРОМ, а перед выводом на экран просто поделите на 10 или 100 снова.

Заодно и профит - давление с десятыми в виде целого влезет в uint16 и будет занимать только две ячейки ЕЕПРОМ, в отличии от флоат, которое требует четырех.

Тест.
4437C7DF
4437C7DF
Всегда ли будет такой результат?

думаю что да.
Для гарантии я бы слегка изменил код округления:

float x=735.123456789;
uint32_t b = x*1000;  // округлили
float r1 = b/1000.0;  

но это только потому, что не уверен, как работает trunс()
В этом коде результат всегда будет одинаков, если числа совпадают до 4 знака после запятой 3 знака

1 лайк

С сотыми в две ячейки можно впихнуть, если использовать unsigned int и перед записью отнимать тысяч 40 (40000). А после чтения прибавлять.

1 лайк

отличная идея

Помните главное - на 8битных ардуино использование флоат надо избегать, оно раздувает и замедляет код

1 лайк

То что замедляет - не реактивный двигатель диагностировать. А то, что раздувает - для Arduino-UNO катастрофа.

Взаимно.

И еще вопрос не по теме. Производитель Arduino-UNO гарантирует 100 000 записей в EEPROM.
Каждый байт выдерживает 100 000 перезаписей или 100 000 обращений вообще???

100тыс это только гарантированный минимум, на практике народ запускал тесты и ЕЕПРОМ выдерживала миллионы перезаписей.
Если вам нужно больше, используйте память FRAM - там число перезаписей достигает 10 в 14 степени. Модули фрам недорогие есть на Али

@Optron , Вам подсказали самое правильное и дешёвое по ресурсам решение:

т.е. просто после умножения на 1000 прибавьте 0.5, преобразуйте к целому и забудьте о перхоти float. В том числе и в eeprom храните целые числа.

Это правильное решение и любые дальнейшие обсуждения – мастурбация.

1 лайк

Ну так проведите численный эксперимент. Хотя бы на миллионе случайных чисел.
А вообще, со случайными - не нужно.
Переберите весь список значений, которые можно получить с датчика.
Например, с аналогового входа можно получить числа от 0 до 1023 с шагом в единицу. Всего 1024 числа. С любым датчиком, думаю, аналогично. Вряд ли датчик может выдать больше 65536 разных значений. Значит, достаточно проверить на всех этих значениях, и у Вас будет исчерпывающий ответ на Ваш вопрос.

Ну, как по мне float вообще необязателен. Датчик выдаёт long, можно с ним и работать

2 лайка