Типы с фиксированной запятой

Десятичные типы с фиксированной запятой

Мы уже видели, как определять типы с плавающей запятой. Однако в некоторых приложениях плавающая запятая не подходит, например, ошибка округления при двоичной арифметики неприемлема или, оборудование не поддерживает инструкции с плавающей запятой. В языке Ада есть десятичные типы с фиксированной запятой, которые позволяют программисту указать требуемую десятичную точность (количество цифр), а также коэффициент масштабирования (степень десяти) и, необязательно, диапазон. Фактически, значения такого типа будут представлены как целые числа, неявно масштабированные с указанной степенью 10. Это полезно, например, для финансовых приложений.

Синтаксис простого десятичного типа с фиксированной запятой:

type <type-name> is delta <delta-value> digits <digits-value>;

В этом случае delta и digits будут использоваться компилятором для вычисления диапазона значений.

Несколько атрибутов полезны при работе с десятичными типами:

Имя атрибута

Значение

First

Наименьшее значение типа

Last

Наибольшее значение типа

Delta

Значение минимального шага типа

В приведенном ниже примере мы объявляем два типа данных: T3_D3 и T6_D3. Для обоих типов значение дельты одинаково: 0.001.

with Ada.Text_IO; use Ada.Text_IO; procedure Decimal_Fixed_Point_Types is type T3_D3 is delta 10.0 ** (-3) digits 3; type T6_D3 is delta 10.0 ** (-3) digits 6; begin Put_Line ("The delta value of T3_D3 is " & T3_D3'Image (T3_D3'Delta)); Put_Line ("The minimum value of T3_D3 is " & T3_D3'Image (T3_D3'First)); Put_Line ("The maximum value of T3_D3 is " & T3_D3'Image (T3_D3'Last)); New_Line; Put_Line ("The delta value of T6_D3 is " & T6_D3'Image (T6_D3'Delta)); Put_Line ("The minimum value of T6_D3 is " & T6_D3'Image (T6_D3'First)); Put_Line ("The maximum value of T6_D3 is " & T6_D3'Image (T6_D3'Last)); end Decimal_Fixed_Point_Types;

При запуске приложения мы видим, что значение дельты обоих типов действительно одинаково: 0.001. Однако, поскольку T3_D3 ограничен 3 цифрами, его диапазон составляет от -0,999 до 0,999. Для T6_D3 мы определили точность 6 цифр, поэтому диапазон от -999,999 до 999,999.

Аналогично определению типа с использованием синтаксиса диапазона (range), поскольку у нас есть неявный диапазон, скомпилированный код будет проверять, что переменные содержат значения, не выходящие за пределы диапазона. Кроме того, если результат умножения или деления десятичных типов с фиксированной запятой меньше, чем значение дельты, известное из контекста, фактический результат будет равен нулю. Например:

with Ada.Text_IO; use Ada.Text_IO; procedure Decimal_Fixed_Point_Smaller is type T3_D3 is delta 10.0 ** (-3) digits 3; type T6_D6 is delta 10.0 ** (-6) digits 6; A : T3_D3 := T3_D3'Delta; B : T3_D3 := 0.5; C : T6_D6; begin Put_Line ("The value of A is " & T3_D3'Image (A)); A := A * B; Put_Line ("The value of A * B is " & T3_D3'Image (A)); A := T3_D3'Delta; C := A * B; Put_Line ("The value of A * B is " & T6_D6'Image (C)); end Decimal_Fixed_Point_Smaller;

В этом примере, результат операции 0.001 * 0.5 будет 0.0005. Ввиду того, что это значение не может быть представленно типом T3_D3 ведь его дельта равна 0.001, реальное значение которое получит переменная A будет равно нулю. Однако, если тип имеет большую точность, то точности арифметических операций будет достаточно, и значение C будет равно 0.000500.

Обычные типы с фиксированной запятой

Обычные типы с фиксированной запятой похожи на десятичные типы с фиксированной запятой в том, что значения, по сути, являются масштабированными целыми числами. Разница между ними заключается в коэффициенте масштабирования: для десятичного типа с фиксированной запятой масштабирование, явно заданное дельтой (delta), всегда является степенью десяти.

Напротив, для обычного типа с фиксированной запятой масштабирование определяется значением small для типа, которое получается из указанного значения delta и, по умолчанию, является степенью двойки. Поэтому обычные типы с фиксированной запятой иногда называют двоичными типами с фиксированной запятой.

Note

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

Синтаксис обычного типа с фиксированной запятой:

type <type-name> is
  delta <delta-value>
  range <lower-bound> .. <upper-bound>;

По умолчанию компилятор выберет коэффициент масштабирования, или small, то есть степень 2, не превышающую <delta-value>.

Например, можно определить нормализованный диапазон между -1.0 и 1.0 следующим образом:

with Ada.Text_IO; use Ada.Text_IO; procedure Normalized_Fixed_Point_Type is D : constant := 2.0 ** (-31); type TQ31 is delta D range -1.0 .. 1.0 - D; begin Put_Line ("TQ31 requires " & Integer'Image (TQ31'Size) & " bits"); Put_Line ("The delta value of TQ31 is " & TQ31'Image (TQ31'Delta)); Put_Line ("The minimum value of TQ31 is " & TQ31'Image (TQ31'First)); Put_Line ("The maximum value of TQ31 is " & TQ31'Image (TQ31'Last)); end Normalized_Fixed_Point_Type;

В этом примере мы определяем 32-разрядный тип данных с фиксированной запятой для нормализованного диапазона. При запуске приложения мы замечаем, что верхняя граница близка к единице, но не ровна. Это типичный эффект типов данных с фиксированной запятой - более подробную информацию можно найти в этом обсуждении Q формата. Мы также можем переписать этот код определения типа:

procedure Normalized_Adapted_Fixed_Point_Type is type TQ31 is delta 2.0 ** (-31) range -1.0 .. 1.0 - 2.0 ** (-31); begin null; end Normalized_Adapted_Fixed_Point_Type;

Мы также можем использовать любой другой диапазон. Например:

with Ada.Text_IO; use Ada.Text_IO; with Ada.Numerics; use Ada.Numerics; procedure Custom_Fixed_Point_Range is type T_Inv_Trig is delta 2.0 ** (-15) * Pi range -Pi / 2.0 .. Pi / 2.0; begin Put_Line ("T_Inv_Trig requires " & Integer'Image (T_Inv_Trig'Size) & " bits"); Put_Line ("The delta value of T_Inv_Trig is " & T_Inv_Trig'Image (T_Inv_Trig'Delta)); Put_Line ("The minimum value of T_Inv_Trig is " & T_Inv_Trig'Image (T_Inv_Trig'First)); Put_Line ("The maximum value of T_Inv_Trig is " & T_Inv_Trig'Image (T_Inv_Trig'Last)); end Custom_Fixed_Point_Range;

В этом примере мы определяем 16-разрядный тип с именем T_Inv_Trig, который имеет диапазон от −𝜋/2 до 𝜋/2.

Для типов с фиксированной запятой доступны все общепринятые операции. Например:

with Ada.Text_IO; use Ada.Text_IO; procedure Fixed_Point_Op is type TQ31 is delta 2.0 ** (-31) range -1.0 .. 1.0 - 2.0 ** (-31); A, B, R : TQ31; begin A := 0.25; B := 0.50; R := A + B; Put_Line ("R is " & TQ31'Image (R)); end Fixed_Point_Op;

Как и ожидалось, R содержит 0,75 после сложения A и B.

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

Следует также отметить, что, хотя подробности также выходят за рамки данного курса, можно явно указать значение small для обычного типа с фиксированной точкой. Это позволяет осуществлять недвоичное масштабирование, например:

type Angle is
  delta 1.0/3600.0
  range 0.0 .. 360.0 - 1.0 / 3600.0;
for Angle'Small use Angle'Delta;