Rationale for Ada 2005: Exceptions, generics etc

RUSTOP
BACKNEXT

ENG

3. Numerics

@ Although Ada 95 introduced unsigned integer types in the form of modular types, nevertheless, the strong typing rules of Ada have not made it easy to get unsigned and signed integers to work together. The following discussion using Ada 95 is based on that in AI-340.

@ Suppose we wish to implement a simulation of a typical machine which has addresses and offsets.

@ We make it a generic

  1        generic
  2                type Address_Type is mod <>;
  3                type Offset_Type is range <>;
  4                ...
  5        package Simulator is
  6                function Calc_Address (Base_Add : Address_Type;
  7                Offset : Offset_Type) return Address_Type;
  8                ...
  9        end Simulator;

@ Addresses are represented as unsigned integers (a modular type), whereas offsets are signed integers. The function Calc_Address aims to add an offset to a base address and return an address.

@ The offset could be negative.

@ Naпvely we might hope to write

  1        function Calc_Address (Base_Add : Address_Type; Offset : Offset_Type) return Address_Type is
  2        begin
  3                return Base_Add + Offset; -- illegal
  4        end Calc_Address;

@ but this is plainly illegal because Base_Add and Offset are of different types.

@ We can try a type conversion thus

  1        return Base_Add + Address_Type (Offset);

@ or perhaps, since Address_Type might have a constraint,

  1        return Base_Add + Address_Type'Base (Offset);

@ but in any case the conversion is doomed to raise Constraint_Error if Offset is negative.

@ We then try to be clever and write

  1        return Base_Add + Address_Type'Base (Offset mod Offset_Type'Base (Address_Type'Modulus));

@ but this raises Constraint_Error if Address_Type'Modulus > Offset_Type'Base'Last which it often will be. To see this consider for example a 32-bit machine with

  1        type Offset_Type is range –(2**31) .. 2**311;
  2        type Address_Type is mod 2**32;

@ in which case Address_Type'Modulus is 2**32 which is greater than Offset_Type'Base'Last which is 2**31–1.

@ So we try an explicit test for a negative offset

  1        if Offset >= 0 then
  2                return Base_Add + Address_Type'Base (Offset);
  3        else
  4                return Base_Add - Address_Type'Base (–Offset);
  5        end if;

@ But if Address_Type'Base'Last < Offset_Type'Last then this will raise Constraint_Error for some values of Offset. Unlikely perhaps but this is a generic and so ought to work for all possible pairs of types.

@ If we attempt to overcome this then we run into problems in trying to compare these two values since they are of different types and converting one to the other can raise the Constraint_Error problem once more. One solution is to use a bigger type to do the test but this may not exist in some implementations. We could of course handle the Constraint_Error and then patch up the answer. The ruthless programmer might even think of Unchecked_Conversion but this has its own problems.

@ And so on – 'tis a wearisome tale.

@ The problem is neatly overcome in Ada 2005 by the introduction of a new functional attribute

  1        function S'Mod (Arg : universal_integer) return S'Base;

@ S'Mod applies to any modular subtype S and returns Arg mod S'Modulus In other words it converts a universal_integer value to the modular type using the corresponding mathematical mod operation. We can then happily write

  1        function Calc_Address (Base_Add : Address_Type; Offset : Offset_Type) return Address_Type is
  2        begin
  3                return Base_Add + Address_Type'Mod (Offset);
  4        end Calc_Address;

@ and this always works.

@ The next topic in the numerics area concerns rounding. One of the problems in the design of any programming language is getting the correct balance between performance and portability. This is particularly evident with numeric types where the computer has to implement only a crude approximation to the mathematician's integers and reals. The best performance is achieved by using types and operations that correspond exactly to the hardware. On the other hand, perfect portability requires using types with precisely identical characteristics on all implementations.

@ An interesting example of this problem arises with conversions from a floating point type to an integer type when the floating type value is midway between two integer values.

@ In Ada 83 the rounding in the midway case was not specified. This upset some people and so Ada 95 went the other way and decreed that such rounding was always away from zero. As well as this rule for conversion to integer types, Ada 95 also introduced a functional attribute to round a floating value. Thus for a subtype S of a floating point type T we have

  1        function S'Rounding (X : T) return T;

@ This returns the nearest integral value and for midway values rounds away from zero.

@ Ada 95 also gives a bit more control for the benefit of the statistically minded by introducing

  1        function S'Unbiased_Rounding (X : T) return T;

@ This returns the nearest integral value and for midway values rounds to the even value.

@ However, there are many applications where we don't care which value we get but would prefer the code to be fast. Implementers have reported problems with the elementary functions where table look-up is used to select a particular polynomial expansion. Either polynomial will do just as well when at the midpoint of some range. However on some popular hardware such as the Pentium, doing the exact rounding required by Ada 95 just wastes time and the resulting function is perhaps 20% slower. This is serious in any comparison with C.

@ This problem is overcome in Ada 2005 by the introduction of a further attribute

  1        function S'Machine_Rounding (X : T) return T;

@ This does not specify which of the adjacent integral values is returned if X lies midway. Note that it is not implementation defined but deliberately unspecified. This should discourage users from depending upon the behaviour on a particular implementation and thus writing non-portable code.

@ Zerophiles will be pleased to note that if S'Signed_Zeros is true and the answer is zero then it has the same sign as X.

@ It should be noted that Machine_Rounding, like the other rounding functions, returns a value of the floating point type and not perhaps universal_integer as might be expected. So it will typically be used in a context such as

  1        X : Some_Float;
  2        Index : Integer;
  3        ...
  4        Index := Integer (Some_Float'Machine_Rounding (X));
  5        ... -- now use Index for table look-up

@ Implementations are urged to detect this case in order to generate fast code.

@ The third improvement to the core language in the numerics area concerns fixed point arithmetic.

@ This is a topic that concerns few people but those who do use it probably feel passionately about it.

@ The trouble with floating point is that it is rather machine dependent and of course integers are just integers. Many application areas have used some form of scaled integers for many decades and the Ada fixed point facility is important in certain applications where rigorous error analysis is desirable.

@ The model of fixed point was changed somewhat from Ada 83 to Ada 95. One change was that the concepts of model and safe numbers were replaced by a much simpler model just based on the multiples of the number small. Thus consider the type

  1        Del : constant := 2.0**(–15);
  2        type Frac is delta Del range –1.0 .. 1.0;

@ In Ada 83 small was defined to be the largest power of 2 not greater than Del, and in this case is indeed 2.0**(–15). But in Ada 95, small can be chosen by the implementation to be any power of 2 not greater than Del provided of course that the full range of values is covered. In both languages an aspect clause can be used to specify small and it need not be a power of 2. (Remember that representation clauses are now known as aspect clauses.) A more far reaching change introduced in Ada 95 concerns the introduction of operations on the type universal_fixed and type conversion.

@ A minor problem in Ada 83 was that explicit type conversion was required in places where it might have been considered quite unnecessary. Thus supposing we have variables F, G, H of the above type Frac, then in Ada 83 we could not write

  1        H := F * G; -- illegal in Ada 83

@ but had to use an explicit conversion

  1        H := Frac (F * G); -- legal in Ada 83

@ In Ada 83, multiplication was defined between any two fixed point types and produced a result of the type universal_fixed and an explicit conversion was then required to convert this to the type Frac.

@ This explicit conversion was considered to be a nuisance so the rule was changed in Ada 95 to say that multiplication was only defined between universal_fixed operands and delivered a universal_fixed result. Implicit conversions were then allowed for both operands and result provided the type resolution rules identified no ambiguity. So since the expected type was Frac and no other interpretation was possible, the implicit conversion was allowed and so in Ada 95 we can simply write

  1        H := F * G; -- legal in Ada 95

@ Similar rules apply to division in both Ada 83 and Ada 95.

@ Note however that

  1        F := F * G * H; -- illegal

@ is illegal in Ada 95 because of the existence of the pervasive type Duration defined in Standard. The intermediate result could be either Frac or Duration. So we have to add an explicit conversion somewhere.

@ One of the great things about Ada is the ability to define your own operations. And in Ada 83 many programmers wrote their own arithmetic operations for fixed point. These might be saturation operations in which the result is not allowed to overflow but just takes the extreme implemented value. Such operations often match the behaviour of some external device. So we might declare

  1        function "*" (Left, Right : Frac) return Frac is
  2        begin
  3                return Standard."*" (Left, Right);
  4        exception
  5                when Constraint_Error =>
  6                        if (0.0 < Left and 0.0 < Right) or (Left < 0.0 and Right < 0.0) then
  7                                return Frac'Last;
  8                        else
  9                                return Frac'First;
 10                        end if;
 11        end "*";

@ and similar functions for addition, subtraction, and division (taking due care over division by zero and so on). This works fine in Ada 83 and all calculations can now use the new operations rather than the predefined ones in a natural manner.

@ Note however that

  1        H := Frac (F * G);

@ is now ambiguous in Ada 83 since both our own new "*" and the predefined "*" are possible interpretations. However, if we simply write the more natural

  1        H := F * G;

@ then there is no ambiguity. So we can program in Ada 83 without the explicit conversion.

@ However, in Ada 95 we run into a problem when we introduce our own operations since

  1        H := F * G;

@ is ambiguous because both the predefined operation and our own operation are possible interpretations of "*" in this context. There is no cure for this in Ada 95 except for changing our own multiplying operations to be procedures with identifiers such as mul and div. This is a very tedious chore and prone to errors.

@ It has been reported that because of this difficulty many projects using fixed point have not moved from Ada 83 to Ada 95.

@ This problem is solved in Ada 2005 by changing the name resolution rules to forbid the use of the predefined multiplication (division) operation if there is a user-defined primitive multiplication (division) operation for either operand type unless there is an explicit conversion on the result or we write Standard."*" (or Standard."/").

@ This means that when there is no conversion as in

  1        H := F * G;

@ then the predefined operation cannot apply if there is a primitive user-defined "*" for one of the operand types. So the ambiguity is resolved. Note that if there is a conversion then it is still ambiguous as in Ada 83.

@ If we absolutely need to have a conversion then we can always use a qualification as well or just instead. Thus we can write

  1        F := Frac'(F * G) * H;

@ and this will unambiguously use our own operation.

@ On the other hand if we truly want to use the predefined operation then we can always write

  1        H := Standard."*" (F, G);

@ Another example might be instructive. Suppose we declare three types TL, TA, TV representing lengths, areas, and volumes. We use centimetres as the basic unit with an accuracy of 0.1 cm together with corresponding consistent units and accuracies for areas and volumes. We might declare

  1        type TL is delta 0.1 range –100.0 .. 100.0;
  2        type TA is delta 0.01 range –10_000.0 .. 10_000.0;
  3        type TV is delta 0.001 range –1000_000.0 .. 1000_000.0;
  4        for TL'Small use TL'Delta;
  5        for TA'Small use TA'Delta;
  6        for TV'Small use TV'Delta;
  7        function "*" (Left : TL; Right : TL) return TA;
  8        function "*" (Left : TL; Right : TA) return TV;
  9        function "*" (Left : TA; Right : TL) return TV;
 10        function "/" (Left : TV; Right : TL) return TA;
 11        function "/" (Left : TV; Right : TA) return TL;
 12        function "/" (Left : TA; Right : TL) return TL;
 13        XL, YL : TL;
 14        XA, YA : TA;
 15        XV, YV : TV;

@ These types have an explicit small equal to their delta and are such that no scaling is required to implement the appropriate multiplication and division operations. This absence of scaling is not really relevant to the discussion below but simply illustrates why we might have several fixed point types and operations between them.

@ Note that all three types have primitive user-defined multiplication and division operations even though in the case of multiplication, TV only appears as a result type. Thus the predefined multiplication or division with any of these types as operands can only be considered if the result has a type conversion.

@ As a consequence the following are legal

  1        XV := XL * XA; -- OK, volume = length Ч area
  2        XL := XV / XA; -- OK, length = volume ч area

@ but the following are not because they do not match the user-defined operations

  1        XV := XL * XL; -- no, volume ? length Ч length
  2        XV := XL / XA; -- no, volume ? length ч area
  3        XL := XL * XL; -- no, length ? length Ч length

@ But if we insist on multiplying two lengths together then we can use an explicit conversion thus

  1        XL := TL (XL * XL); -- legal, predefined operation

@ and this uses the predefined operation.

@ If we need to multiply three lengths to get a volume without storing an intermediate area then we can write

  1        XV := XL * XL * XL;

@ and this is unambiguous since there are no explicit conversions and so the only relevant operations are those we have declared.

@ It is interesting to compare this with the corresponding solution using floating point where we would need to make the unwanted predefined operations abstract as discussed in an earlier paper.

@ It is hoped that the reader has not found this discussion to be too protracted. Although fixed point is a somewhat specialized area, it is important to those who find it useful and it is good to know that the problems with Ada 95 have been resolved.

@ There are a number of other improvements in the numerics area but these concern the Numerics annex and so will be discussed in a later paper.

Rationale for Ada 2005: Exceptions, generics etc

@ENGRUSTOPBACKNEXT

3. Численные данные

@ Хотя в Аде 95 были введены беззнаковые целые типы в форме модульных типов, тем не менее, строгие правила приведения типов Ады весьма затрудняют взаимодействие знаковых и беззнаковых целых типов. Следующее обсуждение использует Аду 95 основанную на AI 340.

@ Предположим, что мы желаем осуществить моделирование типичной машины, у которой есть адреса и смещения.

@ Мы делаем это настраиваемым пакетом:

  1        generic
  2                type Address_Type is mod <>;
  3                type Offset_Type is range <>;
  4                ...
  5        package Simulator is
  6                function Calc_Address (Base_Add : Address_Type;
  7                Offset : Offset_Type) return Address_Type;
  8                ...
  9        end Simulator;

@ Адреса представлены как целые числа без знака (модульный тип), тогда как смещения - знаковые целые числа. Функция Calc_Address добавляет смещение к базовому адресу и возвращает адрес.

@ Смещение может быть отрицательным.

@ Мы могли бы попробовать написать:

  1        function Calc_Address (Base_Add : Address_Type; Offset : Offset_Type) return Address_Type is
  2        begin
  3                return Base_Add + Offset; -- illegal
  4        end Calc_Address;

@ но это явно незаконно, потому что Base_Add и Offset имеют различные типы.

@ Мы можем попробовать преобразование типа:

  1        return Base_Add + Address_Type (Offset);

@ или возможно, так как у Address_Type могло бы быть ограничение,

  1        return Base_Add + Address_Type'Base (Offset);

@ но в любом случае преобразование обречено вызвать исключение Constraint_Error, если значение Offset отрицательно.

@ Тогда попытаемся исхитриться:

  1        return Base_Add + Address_Type'Base (Offset mod Offset_Type'Base (Address_Type'Modulus));

@ но это вызывает исключение Constraint_Error если Offset_Type'Base'Last < Address_Type'Modulus, что нередко случается. Рассмотрим, например, 32-битовую машину с

  1        type Offset_Type is range –(2**31) .. 2**311;
  2        type Address_Type is mod 2**32;

@ в этом случае Address_Type'Modulus равно величине 2 ** 32, что больше чем Offset_Type'Base'Last, которое равно значению 2 ** 31-1.

@ Теперь пробуем сделать явный тест на отрицательное смещение:

  1        if 0 <= Offset then
  2                return Base_Add + Address_Type'Base (Offset);
  3        else
  4                return Base_Add - Address_Type'Base (–Offset);
  5        end if;

@ Но если Address_Type'Base'Last < Offset_Type'Last тогда возбуждается исключение Constraint_Error при некоторых значениях Offset. Вряд ли возможно что этот настраиваемый пакет будет работать для всех возможных пар типов.

@ Если мы попытаемся преодолеть это тогда, мы сталкиваемся с проблемами в попытке сравнить эти две величины, так как они имеют различные типы, и преобразование одного к другому может вызвать Constraint_Error еще раз. Одно из решений состоит в том, чтобы использовать больший тип, чтобы сделать тест, но это, не всегда возможно при некоторых обстоятельствах. Мы могли бы конечно обработать Constraint_Error и затем исправить ответ. Безжалостный программист мог бы даже думать о Unchecked_Conversion, но у этого пути есть свои собственные проблемы.

@ И так далее - это утомительный рассказ.

@ Проблема аккуратно преодолена в Аде 2005 введением нового функционального атрибута:

  1        function S'Mod (Arg : universal_integer) return S'Base;

@ S'Mod относится к любому модульному подтипу S и возвращает Arg mod S'Modulus Другими словами, она конвертирует значение universal_integer в модульный тип, используя соответствующую математическую ультрасовременную операцию mod. Мы можем тогда счастливо написать:

  1        function Calc_Address (Base_Add : Address_Type; Offset : Offset_Type) return Address_Type is
  2        begin
  3                return Base_Add + Address_Type'Mod (Offset);
  4        end Calc_Address;

@ и это всегда работает.

@ Следующая тема в области численных данных касается округления. Одна из проблем в проекте любого языка программирования получить правильный баланс между работой и мобильностью. Это особенно очевидно с числовыми типами, где компьютер должен осуществить только грубое приближение к математическим целым и чисел с плавающей запятой. Лучшая работа достигается при использовании типов и операций, которые соответствуют точно аппаратным средствам. С другой стороны, прекрасная мобильность требует типов точно соответствующим особенностями на всем выполнении.

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

@ В Аде 83 политика округления на полпути не была определена особо. В Аде 95 установили декретом, что такое округление всегда осуществляется вверх. Так же как это правило для преобразования в типы целого числа, Ада 95 также ввела функциональный атрибут отбрасывающий дробную часть числа. Таким образом, для подтипа S плавающей запятой тип T, который мы имеем

  1        function S'Rounding (X : T) return T;

@ Она возвращает самую близкую составную величину с округлением вверх.

@ Ада 95 также дает немного больше контроля в пользу статистических приложений, вводя

  1        function S'Unbiased_Rounding (X : T) return T;

@ Эта функция возвращает самую близкую составную величину и для на полпути выравнивая значение к чётному значению.

@ Однако, есть много приложений, где мы не заботимся об этой величие, где мы, прежде всего, предпочли бы получить быстрый код. Разработчики сообщили о проблемах с элементарными функциями, где используется поиск в таблице для выбора специфическое многочленного расширения. Любой полиномиал сделает точно также находясь в середине некоторого диапазона. Однако, на некоторых популярных аппаратных средствах, таких как Pentium, делая точное округление, требуемое Адой 95 только, напрасно тратится время, и получающаяся функция возможно на 20 % медленнее. Это серьезно в любом сравнении с C.

@ В Аде 2005 эта проблема преодолена введением атрибута:

  1        function S'Machine_Rounding (X : T) return T;

@ Функция не определяет, какая из смежных составных величин возвращается если X лежит на полпути. Отметим, что это не определенное выполнение, а преднамеренно неуказанное. Оно должно препятствовать пользователям строить код зависимым от специфического поведения и, таким образом, написания непортабельного кода.

@ Zerophiles будет рад отметить, что, если S'Signed_Zeros верен и ответ - ноль тогда, у этого есть тот же самый признак как X.

@ Нужно отметить, что Machine_Rounding, как другие функции округления, возвращает значение типа плавающей запятой, а не universal_integer как можно было ожидать. Таким образом, обычно они будут использоваться примерно в следующем контексте

  1        X : Some_Float;
  2        Index : Integer;
  3        ...
  4        Index := Integer (Some_Float'Machine_Rounding (X));
  5        ... -- now use Index for table look-up

@ Реализация однозначно обнаружит этот случай, чтобы произвести быстрый код.

@ Третье усовершенствование основного языка связано с проблемами в области арифметики целых чисел.

@ Эта тема касается немногих людей, но те кто действительно использует её, вероятно чувствуют раздражение состоянием дел.

@ Неприятность с плавающей запятой состоит в том, что оно машино-зависимо, а целые числа это только целые числа. Много прикладных областей использовали некоторую форму масштабирования целых чисел в течение многих десятилетий и Адовская арифметика целых чисел важно в определенных приложениях, где строгий анализ ошибок желателен.

@ Модель фиксированной точки была несколько изменена при переходе от Ады 83 к Аде 95. Одно изменение состояло в том, что понятия образцовых и безопасных чисел были заменены намного более простой моделью, только основанной на умножении маленького числа. Таким образом рассмотрим тип:

  1        Del : constant := 2.0**(–15);
  2        type Frac is delta Del range –1.0 .. 1.0;

@ В Аде 83 маленький был определен, чтобы быть наибольшей степенью 2 не больше чем Del, и в этом случае действительно 2.0 ** (-15). Но в Аде 95, маленький может быть выбран реализацией, чтобы быть любой степенью 2 не больше, чем Del обеспечил конечно, что полный диапазон значений покрыт. На обоих языках пункт аспекта может использоваться, чтобы определить маленький, и это не должна быть степень 2. (Помните, что пункты представления теперь известны как пункты аспекта). более далекое изменение достижения вводило в Аде 95 проблем введение операций на типе universal_fixed и преобразование типа.

@ Небольшая проблема в Аде 83 состояла в том, что явное преобразование типа требовалось в местах, где это, возможно, считали весьма ненужным. Предположим у нас есть переменные F, G, H вышеупомянутого типа Frac, затем в Аде 83 мы не могли написать:

  1        H := F * G; -- illegal in Ada 83

@ здесь необходимо использовать явное преобразование:

  1        H := Frac (F * G); -- legal in Ada 83

@ В Аде 83, умножение было определено между любыми двумя фиксированными типами и приводило к результату типа universal_fixed, и явное преобразование тогда было обязано преобразовывать это в тип Frac.

@ Это явное преобразование, как полагали, было неприятностью, таким образом правило было изменено в Аде 95, было утверждено, что умножение определено только между universal_fixed операндами что давало результат universal_fixed. Неявные преобразования были позволены для обоих операндов, и результат обеспечил, правила решения типа не идентифицировали двусмысленности. Так, так как ожидаемый тип был Frac, и никакая другая интерпретация не была возможна, неявное преобразование было позволено, и так в Аде 95 мы можем просто написать:

  1        H := F * G; -- legal in Ada 95

@ Подобные правила разделяют Аду 83 и Аду 95.

@ Отметим однако что:

  1        F := F * G * H; -- illegal

@ незаконно в Аде 95 из-за существования распространяющейся типа Duration определенным в пакете Standard. Промежуточным результатом мог быть или Frac или Duration. Таким образом, мы должны добавить где-нибудь явное преобразование.

@ Одна из больших вещей об Аде - способность определить Ваши собственные операции. И в Аде 83 много программистов написали свои собственные арифметические операции для чисел с фиксированной точкой. Это могли быть операции насыщения, в которых результату не позволяется переполниться, а только берется предельное значения. Такие операции часто соответствуют поведению некоторого внешнего устройства. Таким образом мы могли бы объявить:

  1        function "*" (Left, Right : Frac) return Frac is
  2        begin
  3                return Standard."*" (Left, Right);
  4        exception
  5                when Constraint_Error =>
  6                        if (0.0 < Left and 0.0 < Right) or (Left < 0.0 and Right < 0.0) then
  7                                return Frac'Last;
  8                        else
  9                                return Frac'First;
 10                        end if;
 11        end "*";

@ и подобные функции для суммирования, вычитания, и деления (берущий заботу по делению на нуль и так далее). Это работает прекрасно в Аде 83, и все вычисления могут теперь использовать новые операции, а не предопределенные естественным способом.

@ Отметим однако что:

  1        H := Frac (F * G);

@ теперь неоднозначно в Аде 83, так как и наше собственное новое "*" и предопределенное "*" являются возможными интерпретациями. Однако, если мы просто пишем более естественное:

  1        H := F * G;

@ тогда нет никакой двусмысленности. Таким образом мы можем программировать в Аде 83 без явного преобразования.

@ Однако, на Аде 95 мы сталкиваемся с проблемой, когда мы вводим свои собственные операции с тех пор

  1        H := F * G;

@ неоднозначно, потому что и предопределенная операция и наша собственная операция - возможные интерпретации "*" в этом контексте. Есть, не исправляют для этого в Аде 95 за исключением изменения наших собственных операций умножения, чтобы быть процедурами с идентификаторами, такими как mul и div. Это - очень утомительная хозяйственная работа склонная к ошибкам.

@ Было сообщено, что из-за этой трудности много проектов использующих фиксированную точку не двигался от Ады 83 к Аде 95.

@ Эта проблема решена в Аде 2005 изменением имени правила разрешающей способности запретить использование предопределенного умножения (деления) операция, если есть определяемое пользователем примитивная операция умножение (деления) или для типа операнда, если нет явного преобразования результата, или мы пишем Standard."*" (или Standard."/").

@ Это означает это, когда нет никакого преобразования как в:

  1        H := F * G;

@ тогда предопределенная операция не может примениться, если есть определяемый пользователем примитив "*" для одного из типов операнда. Таким образом, двусмысленность решена. Отметим, что если есть преобразование тогда, это все еще неоднозначно как и в Аде 83.

@ Если у нас обязательно должно быть преобразование тогда, мы можем всегда использовать квалификацию также или только вместо этого. Таким образом, мы можем написать:

  1        F := Frac'(F * G) * H;

@ и это будет однозначно использовать нашу собственную операцию.

@ С другой стороны, если мы действительно хотим использовать предопределенную операцию тогда, мы можем всегда писать:

  1        H := Standard."*" (F, G);

@ Другой пример мог бы быть поучительным. Предположим, мы объявляем три типа TL, TA, и TV представляющее длины, области, и величины. Мы используем сантиметры как основную единицу с точностью до 0.1 см вместе с соответствующими непротиворечивыми еденицами и точностями для областей и величин. Мы могли бы объявить:

  1        type TL is delta 0.1 range –100.0 .. 100.0;
  2        type TA is delta 0.01 range –10_000.0 .. 10_000.0;
  3        type TV is delta 0.001 range –1000_000.0 .. 1000_000.0;
  4        for TL'Small use TL'Delta;
  5        for TA'Small use TA'Delta;
  6        for TV'Small use TV'Delta;
  7        function "*" (Left : TL; Right : TL) return TA;
  8        function "*" (Left : TL; Right : TA) return TV;
  9        function "*" (Left : TA; Right : TL) return TV;
 10        function "/" (Left : TV; Right : TL) return TA;
 11        function "/" (Left : TV; Right : TA) return TL;
 12        function "/" (Left : TA; Right : TL) return TL;
 13        XL, YL : TL;
 14        XA, YA : TA;
 15        XV, YV : TV;

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

@ Отметим, что у всех трех типов есть примитивные определяемые пользователем операции умножения и деления даже при том, что в случае умножения, TV только появляется в результирующем типе. Таким образом, предопределенное умножение или деление с любым из этих типов как операнды можно рассматривать только, если у результата есть преобразование типов.

@ Как следствие, следующее является правильным:

  1        XV := XL * XA; -- OK, volume = length Ч area
  2        XL := XV / XA; -- OK, length = volume ч area

@ но следующее - нет, потому что они не соответствуют определяемым пользователем операциям:

  1        XV := XL * XL; -- no, volume ? length Ч length
  2        XV := XL / XA; -- no, volume ? length ч area
  3        XL := XL * XL; -- no, length ? length Ч length

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

  1        XL := TL (XL * XL); -- legal, predefined operation

@ и это использует предопределенную операцию.

@ Если мы должны умножить три длины, чтобы получить величину, не сохраняя промежуточную область тогда, мы можем написать:

  1        XV := XL * XL * XL;

@ и это однозначно, так как нет никаких явных преобразований и, таким образом, единственные соответствующие операции это те которые мы объявили.

@ Интересно сравнить это с соответствующим решением, использующим арифметику с плавающей запятой, где мы должны были бы сделать нежелательное предопределенное преобразование операций как обсуждалось в предыдущей статье.

@ Надеюсь, что читатель не нашел это обсуждение слишком длинным. Хотя арифметика с фиксированной точкой несколько специализированная область, для тех важно, кто находит это полезным, и это хорошо, чтобы знать, что проблемы с Адой 95 были решены.

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

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:56

. .