Документация Engee
Notebook

Типы данных с фиксированной точкой

Открыть пример в Engee

В данном примере рассмотрим возможности взаимодействия с числами с фиксированной точкой как в скриптах, так и в моделях.

Числа с фиксированной точкой – это числа, бинарное представление которых ограничено размерами их целой и дробной части. Данные числа не имеют возможности изменения положения точки в битовом представлении числа. Для взаимодействия с такими числами в Engee используется библиотека FixedPointNumbers.

В данной библиотеке для задания чисел с фиксированной точкой имеются две основные команды Nifj и Qifj, где:

  1. i и j – это количество бит, выделяемое на целую и дробную часть;

  2. N – указывает, на то, что число беззнаковое;

  3. Q – указывает, на то, что один бит числа выделяется на знак. Нет целой части положительное.

Примечание: Суммарное количество всех бит, выделяемых на число, должно быть равно: 8, 16, 32 или 64 битам

In [ ]:
using FixedPointNumbers # Подключение библиотеки

Далее рассмотрим несколько примеров задания и применения таких чисел. Первый вариант – это определение беззнакового дробного числа, на целую часть которого выделяется 0 бит.

In [ ]:
x = N0f8(0.8)
Out[0]:
0.8N0f8

Далее прибавим к этому числу целое число с выделенными 2 битами на целую часть. Как мы видим, результирующий тип данных имеет 8 бит на целую и на дробную части. Тем самым мы можем наблюдать автоматическое увелечения разрядности числа, которое реализовано для предотвращения переполнений.

In [ ]:
x + N2f6(2.8)
Out[0]:
3.592N8f8

Далее рассмотрим число со знаком, на целую часть которого выделенно 2 бита, 5 битами на дробную часть и один бит соответственно на знак.

In [ ]:
x = Q2f5(-1.8)
Out[0]:
-1.81Q2f5

Увеличив данное число в три раза, мы получим переполнения по целой части числа, тем самым потеряем знак числа.

In [ ]:
3x
Out[0]:
2.56Q2f5

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

In [ ]:
Q4f11(2.8)-x
Out[0]:
4.6123Q4f11

Если мы хотим выполнить действия над числом без знака и числом со знаком, нам необходимо беззнаковое число привести к виду числа с выделенным битом на знак. Для этого используется команда float.

In [ ]:
x=Q2f5(float(N2f6(2.8)))
Out[0]:
2.78Q2f5
In [ ]:
Q4f11(2.8)+x
Out[0]:
5.5811Q4f11

Теперь перейдём к применению чисел с фиксированной точкой в моделях. Для этого в Engee в настройках блоков предусмотрена возможность выбора выходного типа данных. На рисунке ниже показан интерфейс одного из таких блоков.

image.png

В данном примере реализована модель ПИД-регулятора с применением логики фиксированной точки. Пропорционально-интегрально-дифференцирующий регулятор — устройство в управляющем контуре с обратной связью. Оно используется в системах автоматического управления для формирования управляющего сигнала с целью получения необходимых точности и качества переходного процесса.

На рисунке ниже показана реализованная нами модель.

image_2.png

Перейдём к запуску этой модели.

In [ ]:
function run_model( name_model, path_to_folder )
    
    Path = path_to_folder * "/" * name_model * ".engee"
    
    if name_model in [m.name for m in engee.get_all_models()] # Проверка условия загрузки модели в ядро
        model = engee.open( name_model ) # Открыть модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
    else
        model = engee.load( Path, force=true ) # Загрузить модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
        engee.close( name_model, force=true ); # Закрыть модель
    end

    return model_output
end
Out[0]:
run_model (generic function with 1 method)
In [ ]:
# Запуск модели
run_model( "simple_model_pid_fixed", @__DIR__ )
Building...
Progress 0%
Progress 0%
Progress 5%
Progress 10%
Progress 15%
Progress 20%
Progress 25%
Progress 30%
Progress 35%
Progress 40%
Progress 45%
Progress 50%
Progress 55%
Progress 60%
Progress 65%
Progress 70%
Progress 75%
Progress 80%
Progress 85%
Progress 90%
Progress 95%
Progress 100%
Out[0]:
Dict{String, DataFrame} with 1 entry:
  "SubSystem.command" => 1001×2 DataFrame

Далее для обработки данных моделирования, записанных в CSV, нам понадобится подключить две библиотеки: DataFrames и CSV. Для отображения этих данных задействуем библиотеку Plots.

In [ ]:
# Подключение библиотек
using CSV
using DataFrames
using Plots

Теперь выполним чтение данных из CSV, построим результирующий график и проведём анализ этих данных.

In [ ]:
command_fixed = Matrix(CSV.read("$(@__DIR__)/command_fixed.csv", DataFrame)); #загрузка данных
command_fixed = (command_fixed[:,2]);

Просмотрим структуру записанных данных. Как мы видим, ниже записанные данные представлены в формате String15.

In [ ]:
dump(command_fixed[101])
String15 String15("3.0199Q2f13")

Выделим из этих данных часть, описывающую числа, отделив её от типа данных. С помощью команды, описанной ниже, мы можем просмотреть сам исходный тип данных.

In [ ]:
t = string(command_fixed)[findfirst("Q",string(command_fixed))[1]:end];
t[1:5]
Out[0]:
"Q2f13"

Далее, опираясь на принципы выделения из строки типа, реализуем функцию, которая выделяет из строки числа в формате Float32.

In [ ]:
function str_fi2num(a)
       str = string(a)[1:findfirst("Q",string(a))[1]-1];
       num = parse.(Float32,str); 
       return num
end
Out[0]:
str_fi2num (generic function with 1 method)

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

In [ ]:
command_num = str_fi2num.(command_fixed);
command = Matrix(CSV.read("$(@__DIR__)/command.csv", DataFrame)); #загрузка данных

# Построение графиков 
plot(command[:,2]) 
plot!(command_num)
Out[0]:

Как мы видим, графики приблизительно совпадают. Для того, чтобы точно определить погрешность, найдём их разницу.

In [ ]:
plot(command[:,2]-command_num)
Out[0]:

Разница появляется в пятом знаке после запятой. Также видно, что в результате применяемая нами логика чисел с фиксированной точкой не повлияла на принцип работы ПИД-регулятора. Несмотря на потерю по точности, система стремится к равновесию.

Вывод

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

Блоки, использованные в примере