logo
Языки программирования

9.2. Языковая поддержка вещественных чисел

Все языки программирования имеют поддержку вычислений с плавающей точкой. Переменная может быть объявлена с типом float, а литералы с плава­ющей точкой представлены в форме, близкой к научной нотации:

C

float f1 =7.456;

float f2 = -46.64E-3;

Обратите внимание, что литералы не нужно представлять в двоичной запи­си или в нормализованной форме; это преобразование делается компилято­ром.

Для осмысленных вычислений с плавающей точкой необходимо минимум 32 разряда. Однако часто такой точности недостаточно, поэтому языки под­держивают объявления и вычисления с более высокой точностью. Как мини­мум, поддерживаются переменные с двойной точностью (double-precision), ис­пользующие 64 разряда, а некоторые компьютеры или компиляторы поддер­живают даже более длинные типы. Двойная точность типов с плавающей точ­кой называется double в языке С и Long_Float в Ada.

Запись литералов с двойной точностью может быть разной в различных языках. Fortran использует специальную запись, заменяя Е, предшествующее экспоненте, на D: -45.64D - 3. В языке С каждый литерал хранится с двойной точностью, если же вы хотите задать одинарную точность, то используется суффикс F. Обратите на это внимание, если вы храните большой массив кон­стант с плавающей точкой.

Ada вводит новое понятие — универсальные типы (universal types) — для об­работки переменной точности представления литералов. Такой литерал как 0.2 хранится компилятором с потенциально неограниченной точностью (вспомните, что 0.2 нельзя точно представить как двоичное число). Фактиче­ски при использовании литерала он преобразуется в константу с той точно­стью, которая нужна:

Ada

PI_F: constant Float := 3.1415926535;

PI_L: constant Long_Float :=3.1415926535;

PI: constant := 3.1415926535;

F: Float := PI; -- Преобразовать число к типу Float

L: Long_Float := PI; -- Преобразовать число к типу Long_Float

В первых двух строках объявляются константы именованных типов. Третье объявление для PI называется именованным числом (named number) и имеет универсальный вещественный тип. Фактически, в инициализациях PI преоб­разуется к нужной точности.

Четыре арифметические операции (+,-,* и /), так же как и операции от­ношения, определены для типов с плавающей точкой. Такие математиче­ские функции, как тригонометрические, могут быть определены в рамках языка (Fortran и Pascal) или поставляться с библиотеками подпрограмм (С и Ada).

Плавающая точка и переносимость

При переносе программ, использующих плавающую точку, могут возникнуть трудности из-за различий в определении спецификаторов типа. Ничто не ме­шает компилятору для С или Ada использовать 64 разряда для представления float (Float) и 128 разрядов для представления double (Long_Float). Перенос на другую машину проблематичен в обоих направлениях. При переносе с маши­ны, где реализовано представление float с высокой точностью на машину, ис­пользующую представление с низкой точностью, все типы float должны быть преобразованы в double, чтобы сохранить тот же самый уровень точности. При переносе с машины с низкой точностью на машину с высокой точностью может потребоваться противоположное изменение, потому что выполнение с избыточной точностью приводит к потерям времени и памяти.

Простейшее частное решение состоит в том, чтобы объявлять и использо­вать искусственный тип с плавающей точкой; в этом случае при переносе про­граммы нужно будет изменить только несколько строк:

typedef double Real; /* С */

subtype Real is Long_Float; -- Ada

Решение проблемы переносимых вычислений с вещественными числами в Ada

см. в разделе 9.4.

Аппаратная и программная плавающая точка

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

Компьютер без соответствующих аппаратных средств может все же выпол­нять вычисления с плавающей точкой, используя библиотеку подпрограмм, которые эмулируют (emulate) команды с плавающей точкой. Попытка выпол­нить команду с плавающей точкой вызовет прерывание «несуществующая ко­манда», которое будет обработано с помощью вызова соответствующей под­программы эмуляции. Само собой разумеется, что это может быть очень не­эффективно, поскольку существуют издержки на прерывание и вызов под­программы, не говоря о самом вычислении с плавающей точкой.

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

Смешанная арифметика

В математике очень часто используются смешанные арифметические опера­ции с целыми и вещественными числами: мы пишем А = 2pi*r, а не А = 2.0pi*r. При вычислении смешанные операции с целыми числами и числами с плава­ющей точкой должны выполняться с некоторой осторожностью. Предпочти­тельнее вторая форма, потому что 2.0 можно хранить непосредственно как константу с плавающей точкой, а литерал 2 нужно было бы преобразовать к представлению с плавающей точкой. Хотя обычно это делается компилято­ром автоматически, лучше точно написать, что именно вам нужно.

Другой потенциальный источник затруднений — различие между целочис­ленным делением и делением с плавающей точкой:

Ada

I: Integer := 7;

J: Integer := I / 2;

К: Integer := lnteger(Float(l) / 2.0);

Bыражение в присваивании J задает целочисленное деление; результат, ко-нечно, равен 3. В присваивании К требуется деление с плавающей точкой: ре-зультат равен 3.5, и он преобразуется в целое число путем округления до 4.

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

int i = 7;

C

int j = i/2;

int k = (int) ((float i)/ 2.0);

Здесь 3 присваивается как j, так и k, потому что значение 3.5 с плавающей точкой обрезается, а не округляется!

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

C

int k = i/2.0;

«Продвижение» целочисленного i к плавающему типу вполне распознаваемо, и тем не менее для лучшей читаемости программ в присваиваниях (в отличие от инициализаций) преобразования типов лучше задавать явно:

C

k=(int)i/2.0;

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

Если важна эффективность, реорганизуйте смешанное выражение так, чтобы вычисление оставалось по возможности простым как можно дольше. Рассмотрим пример (вспомнив, что литералы в С рассматриваются как dou­ble):

C

int i,j,k,l; float f= 2.2 * i * j * k * I;

Здесь было бы выполнено преобразование i к типу double, затем умножение 2.2 * i и так далее для каждого целого числа, преобразуемого к типу double. Наконец, результат был бы преобразован к типу float. Эффективнее было бы написать:

C


int i j, k, I; I

float f=2.2F*(i*J*k*l);

чтобы гарантировать, что сначала будут перемножены целочисленные пере­менные с помощью быстрых целочисленных команд и что литерал будет хра­ниться как float, а не как double. Конечно, такая оптимизация может привести к целочисленному переполнению, которого могло бы не быть, если вычисле­ние выполнять с двойной точностью.

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

Yandex.RTB R-A-252273-3
Yandex.RTB R-A-252273-4