Арифметика с фиксированной точкой (Fixed-Point) в Engee
Страница в процессе разработки. |
В мире численных вычислений подавляющее большинство задач решается с использованием чисел с плавающей точкой (Float32
, Float64
). Однако в реальных системах — таких, как микроконтроллеры, DSP, ПЛИС или ASIC — использование Float-типов может быть нежелательным или невозможным. Например, в микроконтроллерах семейства STM32 базовые модели не имеют аппаратной поддержки float
, и операции с плавающей точкой в них выполняются значительно медленнее, чем целочисленные. В таких случаях применяется арифметика с фиксированной точкой (Fixed-Point
), при которой дробные значения кодируются в целочисленном формате с заранее заданной длиной дробной части.
Числа с фиксированной точкой (Fixed-Point
) — это способ представления дробных значений с помощью обычных целых чисел и заранее заданного масштаба (количества бит под дробную часть). Вместо ресурсоемкой арифметики с плавающей точкой (Float32
, Float64
), здесь используется простой целочисленный формат, где «двоичная запятая» сдвигается на заданное число бит.
Например, если длина дробной части , то число хранится как целое значение , потому что по формуле пересчета внутреннего значения (stored_integer
) в реальное (real_value
) получим . По формуле видно, что внутреннее значение сохраняется и равно , а при вычислениях оно интерпретируется как .
Такая арифметика имеет следующие преимущества:
-
Меньшее потребление ресурсов (актуально для микроконтроллеров, ПЛИС и ASIC);
-
Контролируемая точность и диапазон значений;
-
Предсказуемое поведение при округлениях[1] и переполнениях[2];
-
Поддержка генератора кода на Verilog (HDL) и Cи.
Работа с фиксированной точкой в Engee
Для работы с фиксированными точками в Engee используется собственный пакет EngeeFixedPoint.jl
, который заменяет стандартный для Julia пакет FixedPointNumbers.jl
. В отличие от классического пакета, EngeeFixedPoint.jl
предоставляет расширенные возможности и точный контроль над представлением и поведением чисел с фиксированной точкой — что особенно важно в системах с ограниченными ресурсами, при переносе вычислений в HDL, а также в задачах строгой точности.
Пакет EngeeFixedPoint.jl является стандартным пакетом Engee и входит в пользовательское окружение по умолчанию, поэтому не требует явного вызова (через import /using ) в коде.
|
В Engee тип числа с фиксированной точкой выглядит следующим образом:
Fixed{S, W, f, T} <: FixedPoint
где:
Такой формат позволяет точно задать как число будет храниться, интерпретироваться и участвовать в вычислениях за счет безопасной типизации и четкого поведения на всех этапах обработки данных.
Для удобства EngeeFixedPoint.jl
предлагает несколько способов задать тип фиксированной точки — от полного ручного задания до автоматического вывода.
S, W, f, T = 1, 25, 10, Int32
dt1 = Fixed{S, W, f, T}
dt2 = fixdt(S, W, f)
dt3 = fixdt(Fixed{S, W, f})
dt4 = fixdt(dt2)
println(dt1 == dt2 == dt3 == dt4) # true
где:
-
dt1 = Fixed{S, W, f, T}
— полное описание вручную; -
dt2 = fixdt(S, W, f)
— упрощенное создание, тип выбирается автоматически; -
dt3 = fixdt(Fixed{S, W, f})
— получение типа на основе уже существующего описания; -
dt4 = fixdt(dt2)
— повторное использование, создает копию из существующего типа.
Все эти варианты создают один и тот же тип Fixed{1, 25, 10, Int32}
, и могут использоваться в зависимости от задачи:
-
Полное описание (
dt1
) удобно, когда нужен контроль над всеми параметрами; -
Упрощенный способ (
dt2
) подходит для типичных случаев и сокращает код; -
Получение типа из типа (
dt3
) полезно при генерации кода или типизации данных; -
Повторное использование (
dt4
) помогает работать с параметризированными структурами без повторного ввода параметров.
Конструкторы типа Fixed
Далее рассмотрим конкретные сценарии работы с фиксированными точками в Engee.
Так, можно напрямую задать тип и передать значение:
x = Fixed{1, 15, 2}(25)
Вывод:
fi(6.25, 1, 15, 2)
Это означает, что — целочисленное представление (stored_integer
), а реальное значение (real_value
) будет равно в соответствии с формулой .
Fixed{S, W, f}(i::T)
Конструктор создания фиксированной точки по целочисленному представлению Fixed{S, W, f}(i::T)
принимает:
-
Параметры формата:
S
(знаковость),W
(ширина в битах),f
(дробная часть); -
Целочисленное значение
i
типаT
(внутреннее представление).
S, W, f = 1, 15, 2 # знаковый, 15 бит, 2 бита дробной части
i = 25
x = Fixed{S, W, f}(i) # создание из целого числа
Вывод:
fi(6.25, 1, 15, 2) # эквивалентное представление
Fixed{S, W, f, T1}(i::T2)
Аналогичный с предыдущим конструктор, с возможностью явного указания типа хранения. Тип будет автоматически подобран в соответствии с параметрами S
, W
, f
, независимо от указанного T1
.
T = Int128
x = Fixed{S, W, f, T}(i) # с указанием типа хранения
Вывод:
fi(6.25, 1, 15, 2) # результат идентичен
Конструкторы из FixedPointNumbers.jl
Несмотря на использование нового пакета EngeeFixedPoint.jl
, в нем осталась совместимость с пакетом FixedPointNumbers.jl
для поддержки ряда конструкторов. Поддерживаются только знаковые типы.
Поддерживаются:
-
Fixed{T, f}(i::Integer, _)
— конструктор по целочисленному представлению. Принимает типT
и параметрf
; -
Fixed{T, f}(value)
— конструктор по реальному значению (float
).
Пример:
T = Int32
x1 = Fixed{T, f}(i, nothing) # из целого числа
x2 = Fixed{T, f}(i) # из вещественного числа
Вывод:
6.25 # результат первого конструктора
25.0 # результат второго конструктора
Вспомогательные методы fi
Основной удобный способ создания чисел с фиксированной точкой это вспомогательные методы fi
. В отличие от конструкторов, они автоматически определяет параметры представления.
x1 = fi(3.37, 0, 63, 4) # Полный формат с явным указанием параметров
x2 = fi(3.37, fixdt(0, 63, 4)) # Через тип данных
x3 = fi(3.37, 0, 63) # С автоматическим определением дробной части
x4 = fi(100, 1, 8, 5) # Демонстрация обработки переполнения
Вывод:
3.375 # значение с учетом округления
true # x1 и x2 идентичны
3.37 # с автоматическим подбором
3.96875 # результат насыщения при переполнении
Комплексные числа
Полная поддержка комплексных чисел с фиксированной точкой с теми же методами создания через fi
:
s, w, f = 1, 62, 7;
v = 2.5 - 3.21im
x1 = fi(v, s, w, f)
x2 = fi(v, fixdt(s, w, f))
x3 = fi(v, s, w)
println(x1)
println(x1 == x2)
println(x3)
println()
Вывод:
fi(2.5, 1, 62, 7) - fi(3.2109375, 1, 62, 7)*im
true
fi(2.5, 1, 62, 59) - fi(3.21, 1, 62, 59)*im
Работа с массивами и матрицами
Библиотека предоставляет полную поддержку векторных и матричных операций с числами фиксированной точки. Все операции сохраняют тип элементов и автоматически применяют указанные параметры точности ко всем элементам массива.
Векторы
Создание и работа с одномерными массивами. Параметры фиксированной точки применяются ко всем элементам:
s, w, f = 1, 62, 7 # знаковый тип, 62 бита, 7 бит дробной части
v = [1, 2, 3] # исходный вектор
# Разные способы создания:
x1 = fi(v, s, w, f) # с явным указанием параметров
x2 = fi(v, fixdt(s, w, f)) # через тип данных
x3 = fi(v, s, w) # с автоматическим определением дробной части
println(x1)
println(x1 == x2)
println(x3)
Вывод:
Fixed{1, 62, 7}[1.0, 2.0, 3.0]
true
Fixed{1, 62, 59}[1.0, 2.0, 3.0]
Комплексные матрицы
Полноценная поддержка комплексных чисел в многомерных массивах:
s, w, f = 1, 62, 7
m = [im 2.5; -1.2im 25-im]
# Рабочие способы создания:
x1 = fi(m, s, w, f) # с явным указанием параметров
x2 = fi(m, fixdt(s, w, f)) # через тип данных
println(x1)
println(x1 == x2)
Вывод:
Complex{Fixed{1, 62, 7, Int64}}[fi(0.0, 1, 62, 7) + fi(1.0, 1, 62, 7)*im fi(2.5, 1, 62, 7) + fi(0.0, 1, 62, 7)*im; fi(0.0, 1, 62, 7) - fi(1.203125, 1, 62, 7)*im fi(25.0, 1, 62, 7) - fi(1.0, 1, 62, 7)*im]
true
Базовые операции и методы
Описываются методы для работы с числами фиксированной точки, позволяющие определить допустимый диапазон значений и базовые свойства.
Граничные значения
Методы typemax
и typemin
позволяют определить максимальное и минимальное возможные значения для конкретного типа фиксированной точки.
dt = fixdt(0, 25, -2) # беззнаковый тип с 25 битами и дробной частью -2
x = fi(1.5, dt) # создаем число фиксированной точки
println(typemax(x)) # 1.34217724e8 - максимальное представимое значение
println(typemin(x)) # 0.0 - минимальное значение для беззнакового типа
Математические операции
Система автоматически подбирает оптимальный формат результата операций, сохраняя точность и предотвращая переполнение. Поддерживаются все основные арифметические операции (сложение, вычитание, умножение, деление):
x1 = fi(1.5, 0, 15, 3)
x2 = fi(1.5, 1, 25, 14)
y1 = x1+x2
y2 = x1-x2
y3 = x1*x2
y4 = x1/x2
println(y1)
println(y2)
println(y3)
println(y4)
println(typeof(y1))
println(typeof(y2))
println(typeof(y3))
println(typeof(y4))
println(x1 == x2)
println(x1 <= x2)
println(x1 > x2)
Вывод:
3.0
0.0
2.25
0.0
Fixed{1, 28, 14, Int32}
Fixed{1, 28, 14, Int32}
Fixed{1, 40, 17, Int64}
Fixed{1, 25, -11, Int32}
true
true
false
Округление
Различные стратегии округления позволяют контролировать точность вычислений. По умолчанию используется округление RoundNearestTiesUp.
x = fi(1.5, 1, 14, 3) # знаковый, 14 бит, 3 бита дробной части
println(round(x)) # 2.0 - округление к ближайшему целому (1.5 → 2)
println(trunc(x)) # 1.0 - отбрасывание дробной части
println(ceil(x)) # 2.0 - округление вверх к большему целому
println(floor(x)) # 1.0 - округление вниз к меньшему целому
где:
-
round
— банковское округление (к ближайшему четному при0.5
); -
trunc
— отбрасывание дробной части; -
ceil
— всегда в большую сторону; -
floor
— всегда в меньшую сторону.
Преобразование типов (конвертация)
Конвертация в стандартные типы данных полезна при взаимодействии с другими библиотеками. При преобразовании учитываются правила округления.
x = fi(1.5, 1, 12, 4)
y1 = Int64(x)
y2 = UInt8(x)
y3 = Float64(x)
y4 = convert(fixdt(0, 5, 2), x)
println(y1)
println(y2)
println(y3)
println(y4)
println(typeof(y1))
println(typeof(y2))
println(typeof(y3))
println(typeof(y4))
Вывод:
1
1
1.5
1.5
Int64
UInt8
Float64
Fixed{0, 5, 2, UInt8}
Вывод
Таким образом, пакета EngeeFixedPoint.jl
предоставляет следующие преимущества:
-
Расширенная система типов:
-
Полная поддержка как знаковых, так и беззнаковых чисел;
-
Произвольная разрядность (любые битовые размеры, не только 8/16/32/64/128);
-
Гибкая настройка дробной части (включая отрицательные значения и случаи, когда , длина дробной части, больше длины слова ).
-
-
Улучшенные правила вывода типов:
-
Автоматическое определение формата для результатов математических операций (
+
,−
,*
,/
); -
Снятие ограничений на автоматическое наследование типов в блоках Деление/Сложение/Деление/Коэффициент усиления.
-
-
Платформо-зависимая кодогенерация:
-
Разные правила наследования типов для целевых платформ (Си или Verilog);
-
Предсказуемое поведение при переполнении 128-битной границы (в отличие от аналогов).
-
-
Расширенный функционал:
-
Оптимизированная работа с массивами и матрицами (см. Работа с массивами и матрицами);
-
Полная поддержка комплексных чисел (см. Комплексные числа);
-
Эффективные методы округления (round/trunc/ceil/floor, подробнее см. Округление).
-
Поддержка базовых методов (zero/one/typemin/typemax), см. Граничные значения.
-
f
). Поскольку числа с фиксированной точкой не могут точно представить все возможные дробные значения, при операциях результат округляется согласно заданной стратегии (например, до ближайшего значения или с усечением).
W
) и знаковости (S
). В таких случаях применяется стратегия обработки переполнения: насыщение (saturation), при котором значение ограничивается максимальным/минимальным допустимым, либо усечение или вывод ошибки.
Int8
, Int16
, Fixed{1, 16, 4}
.
UInt8
, UInt16
, Fixed{0, 16, 4}
.