Главная arrow Программирование arrow AVR arrow AVR - как избавиться от чисел с плавающей точкой Friday, June 23 2017  
ГлавнаяКонтактыАдминистрированиеПрограммированиеСсылки
UK-flag-ico.png English Version
GERMAN-flag-ico.png Die deutsche Version
map.gif карта сайта
нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Поделиться:

AVR - как избавиться от чисел с плавающей точкой Версия для печати
Написал microsin   
22.05.2007

Одно из сильных ограничений AVR (на платформе MCS51 это сказывается не так) - быстрый расход памяти при написании программ на Си. Что поделаешь, за удобства Си и скорость RISC надо платить. Еще больший соблазн - использование для вычислений (например, при измерении напряжений с помощью ADC) чисел float (числа с плавающей запятой). Несмотря на то, что эти вычисления не точны, их слишком просто и удобно применять - можно делить и умножать, не задумываясь о переполнении, и легко представлять результаты вычисления в формате, понятном человеку.

Но плата за float слишком высока - линкер добавляет код библиотек математики, и память программ кончается очень быстро. Другое ограничение - вычисления с float выполняются слишком медленно. Если нет возможности реализовать алгоритм программы с применением чисел float (из-за вышеуказанных ограничений по объему кода и скорости), одним из вариантов решения проблемы является переход на числа с фиксированной запятой. Их математические операции (вычитание, сложение, деление, умножение) ничем не отличаются от математических операций с простыми целыми числами, код получается компактный и быстрый.

Число с фиксированной запятой (обычно байт или слово из двух байт) состоит из целой части (находится в старших битах 8 или 16-разрядного числа) и дробной части (находится в младших битах). Пример числа с фиксированной запятой указан на рисунке, с разрядностью в 8 бит (1 байт).
fixed_comma_digit.jpg

Перед использованием чисел с фиксированной запятой главное - выбрать разрядность числа (байт или слово), и также выбрать положение запятой. С разрядностью вроде все понятно - если возьмем слово (16 бит), вычисления будут точнее, но скорость упадет и объем кода вырастет (и то и другое не меньше чем в 2 раза), а если возьмем байт (8 бит), то получим максимальное быстродействие и самый маленький код, но ухудшится точность. Как обычно, нужен компромисс, и Ваша задача - принять верное решение. Положение запятой никак не влияет на объем кода и быстродействие при математических операциях. Она просто распределяет соотношение точности между целой и дробной частью. Обычно вычисляют, сколько разрядов надо выделить на целую часть, и оставшуюся часть достается дробной части.

Например, наше 8-битное число будет хранить напряжение, которое будет меняться от 0 до 5 вольт. Число от 0 до 5 может кодироваться минимум тремя разрядами, поэтому разряды 7, 6 и 5 будут хранить целую часть (3 разряда), а разряды 4, 3, 2, 1 и 0 - остаются под дробную часть (5 разрядов). Число в дробной части будет показывать, сколько 1/32 от вольта будет в дробной части. Например, если в битах 7, 6 и 5 будет число 4, а в битах 4, 3, 2, 1 и 0 - число 30, то это будет кодировать напряжение 4.9375 вольта (0.9375 = 30/2^5 = 30/32). Значение байта при этом будет 100.11110b или 0x9E.

При вычислениях с фиксированной запятой (как и с простыми вычислениями на целых числах) нужно применять особые правила:

1. В результате сложения двух чисел возможно появление дополнительного разряда. Это происходит, если произошло переполнение. Если возможность переполнения нужно учитывать, то дополнительный 1 бит числа надо где-то хранить.

2. Результат умножения двух 8-битных чисел хранить в 16-разрядном числе, двух 16-битных в 32-разрядном, и т. п. (разрядность при умножении складывается). 

3. При делении (малого числа на большое особенно) нужно предварительно делимое умножить на константу. Самое простое - сдвинуть число влево на нужное число раз (каждый сдвиг умножает на 2), поместить сдвинутый результат в число вдвое большей разрядности, и потом уже спокойно делить. В результате получим число с фиксированной запятой. Например, если делим 8-разрядное целое делимое, сдвинутое влево на 5 разрядов (получили 16-разрядное делимое), на целый делитель, то получаем дробное 16-битное число с фиксированной запятой, где запятая находится между 5 и 4 разрядами.

4. Лучше как можно больше пользоваться предварительно вычисленными на этапе компилирования константами, чтобы убрать код, который будет их генерировать. Например, если мы должны сравнить напряжение на аккумуляторе с напряжением 1.05 вольт, то это напряжение 1.05 вольт лучше сразу представить в нужном формате и определить директивой #define.

Когда нужно отобразить число с фиксированной запятой как набор десятичных цифр, действуют по простому алгоритму:
- сначала берут целую часть, и преобразуют её в символьный вид обычным образом.
- за целой частью рисуют запятую (или точку).
- берут дробную часть, приводят её к десятичной дроби, просто домножая и числитель, и знаменатель дробной части на дробное число (при этом значение дроби, как мы знаем, не изменится) - константу. Эта константа выбирается так, чтобы знаменатель стал числом - степенью десятки, а не двойки - при этом получится десятичная дробь. Фраза "домножая на дробное число" означает набор целочисленных операций (сначала умножить на целую константу, а потом разделить на целую константу), результат которых и будет это умножение на дробное число. При операциях умножения и деления либо множитель будет четным, либо делитель, либо они оба - и множитель, и делитель, будут нечетными (мы ведь формируем таким образом умножение на нецелое число). В качестве четной удобно использовать константу, являющуюся степенью двойки (2, 4, 8 и т. д.), потому что умножение и деление на эту константу заменяется простым сдвигом влево и вправо соответственно.
- после этого полученное значение числителя переводим в набор десятичных цифр и приписываем их после запятой.

Чтобы пояснить эти "премудрости" возьмем все тот же пример - переведем дробное число с фиксированной запятой 100.11110b (== 0x9E, наши 4.9375 вольта) в символьное представление:
- целая часть у нас равна 100b, т. е. 4, рисуем цифру 4
- рисуем за целой частью дробную точку: 4.
- берем дробную часть 11110b. Она равна 30, т. е. наша дробь - числитель 30, а знаменатель 32. Наша задача - подобрать такое дробное число, чтобы при его умножении на знаменатель 32 получилось число, которое можно представить степенью десятки, причем какая была степень десятки, столько десятичных знаков после запятой и получим. Пусть надо получить 3 десятичных знака после запятой, т. е. знаменатель 32 приводим к 1000. Число, на которое нужно домножить и числитель, и знаменатель, равно 1000/32 = 31.25. Отлично, но как умножить на дробное число, имея в рапоряжении только целочисленную арифметику? Все просто - умножаем сначала на 125, а потом делим на 4 (т. к. 125/4 равно 31.25). Именно в таком порядке - сначала умножение (понадобится временное 16-битное число для хранения результата умножения), а потом деление, чтобы не потерять точность при отбрасывании остатка деления. На 125 умножаем как обычно, а делим на 4, сдвигая число на 2 бита вправо. Итак, 30 * 31.25 = (30 * 125) / 4 = 3750 / 4 = 937.5, округляем до 938. Таким образом, дробь 30/32 превратилась в дробь 938/1000.
- числитель 938 дописываем после запятой, получаем 4.938.

При подборе констант домножения/деления удобно использовать смекалку и старые добрые электронные таблицы Microsoft Excel.

Последнее обновление ( 14.11.2013 )
 

Комментарии  

  1. #3 Владимир
    2013-11-1400:04:55 Спасибо
  2. #2 Goodman
    2011-03-2911:24:27 Good!
  3. #1 доступно
    2008-10-0421:11:00

Добавить комментарий

:D:lol::-);-)8):-|:-*:oops::sad::cry::o:-?:-x:eek::zzz:P:roll::sigh:

Защитный код
Обновить

< Пред.   След. >

Top of Page
 
microsin © 2017