Число с плавающей запятой в двоичном представлении по стандарту IEE754. Или как вычислить Float из двух Word c Modbus
Где это встречается?
Большинство программистов в наше время пишут код без понятия, что происходит там "под капотом", банально как хранятся в памяти те или иные типы переменных и у них это не вызывает никаких проблем. А что будет, если оставить только базовые операторы?
Дано: некий измерительный преобразователь отправляет float по протоколу modbus в виде двух регистров word, мы их получаем на ПЛК.
Задача: полученные два слова сконвертировать обратно в float.
Float по стандарту IEE754
Стандарт IEE754 определяет как представлять отрицательные и положительные числа с плавающей точкой. Множество статей в интернете описывает тонкости это стандарта с приложенными математическими формулами и подробным описаниям.
Самое главное, что нам нужно знать из этого стандарта:
- float это 32 бита
- где 1-й бит - знак, следующие 8 - порядок, последние 23 - мантисса
- зная эти данные и подставив их в формулу можно получить искомое значение
Как обычно это решается?
Обычно это решается быстро и легко через указатели или через встроенные функции конвертации. Что в принципе не требует знаний о стандарте.
Я же за неимением онного использовал битовые операции.
Решение с помощью битовых операций
Зная формат, несложно получить знак, порядок и мантиссу из битового представления числа. Делается это с помощью нескольких битовых масок. Затем полученные числа подставляются в формулу и вычисляется результат.
PROGRAM PLC_PRG
VAR real_dword, sign, mant, expb, bmask1, bmask2, bmask3: DWORD;
word1_input, word2_input: WORD;
float_output, signf, expf: REAL;
mantf: REAL;
END_VAR
word1_input := 11047;
word2_input := 16964;
bmask1 := 16#FF;
bmask2 := 16#7FFFFF;
bmask3 := 16#800000;
(* Складываем два слова в одно двойное *)
real_dword := SHL(WORD_TO_DWORD(word2_input), 16) + WORD_TO_DWORD(word1_input);
(* Находим знак *)
sign := SHR(real_dword, 31);
IF sign > dword#0 THEN
signf := -1.0;
ELSE
signf := 1.0;
END_IF;
(* Находим порядок *)
expb := SHR(real_dword, 23) AND bmask1;
(* Находим мантиссу *)
IF(expb <> dword#0 ) THEN
mant := (real_dword AND bmask2) OR bmask3;
ELSE
mant := SHL((real_dword AND bmask2), 1);
END_IF;
(* Подставляем полученные данные в формулу *)
mantf := (DWORD_TO_REAL(mant)) * (EXPT(REAL#2.0, DINT#-23));
expf := expt(real#2.0 , dword_to_dint(expb - dword#127));
float_output := signf * expf * mantf;
Этот код немного отличается от изначального, который я писал для AXC 1050 в PC Worx, из-за невозможности протестировать его на сегодняшний день нигде кроме эмулятора в CodeSys.
Это несущественно, разница лишь в способе объявления переменных
Чем можно проверить свое решение
Пока гуглил все эти стандарты и способы решений, нашел один интересный Excel документ от Schneider Electric. С его помощью вы можете быстро проверить свое решение.