Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

osn_progr_final

.pdf
Скачиваний:
37
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

x 2n 1 k p

де:

n - кількість біт, відведених для характеристики, p - порядок числа,

k - поправочний коефіцієнт фірми IBM, рівний -1 .

Таблиця 4

 

 

 

 

 

Тип

Характеристика

Кількість біт на характеристику

 

float

x = 2^7 + p - 1

8

 

double

x = 2^10 + p - 1

11

 

long double

x = 2^14 + p - 1

15

 

 

 

 

 

 

 

 

Для простоти, розглянемо тип float, тому що він є самим коротким. Представлення інших типів відрізняються від нього тільки кількісно.

Відразу має сенс сказати, що в процесорах Intel байти в багатобайтних значеннях переставляються так, що молодший байт йде першим, а старший – останнім.

У мантисі зберігається двійкове ціле число. Щоб одержати істинне значення мантиси, до неї треба умовно додати ліворуч одиницю з крапкою. Таким чином, маючи 23 двійкових розряди, ми записуємо числа з точністю до 24-ох двійкових розрядів. Це зв'язано з тим, що використовується нормалізоване представлення дійсного числа. Нормалізація означає, що мантиса (для двійкового представлення), крім випадку, коли вона дорівнює 0, повинна знаходитися в інтервалі

2 1 M 1.

Наведений метод нормалізації є класичним методом, при якому результат нормалізації представляється у вигляді правильного дробу, тобто з одиницею після крапки і нулем у цілій частині числа. Тоді ненульова мантиса будь-якого числа з плаваючою крапкою повинна починатися з двійкової одиниці. Тому ця одиниця враховується, однак не записується в мантису. Її часто називають прихованою одиницею.

У комп'ютерах на базі процесорів Intel, нормалізована мантиса містить свій старший біт ліворуч від крапки. Іншими словами, нормалізована мантиса належить інтервалу 1 M 2 . У пам'яті машини для даних типу float, double цей біт не зберігається, тобто є "схованим" і

151

використовується для збільшення порядку . Для додатніх і від”ємних чисел нормалізована мантиса в пам'яті представлена в прямому коді.

Старший біт у представленні чисел у форматі з плавачою крапкою є знаковим, і за прийнятою згодою нуль позначає додатне число, а одиниця – від”ємне.

Якщо всі біти характеристики дорівнюють одиниці, а мантиси – нулю, то ми одержуємо комбінацію, відому як INF (від англійського Infinity – нескінченність). Ця комбінація використовується тоді, коли результат обчислень перевищує максимально допустиме форматом число. У залежності від значення знакового біта, нескінченність може бути додатною чи від”ємною. Якщо ж при такому порядку в мантисі хоч один біт не дорівнює нулю, така комбінація називається NAN (Not A Number – не число). Спроби використання комбінацій NAN чи INF приводять до помилки часу виконання.

З врахуванням усіх цих правил комбінація, коли і у мантисі, і в порядку всі біти дорівнюють нулю, дає 00. З математичної точки зору ця комбінація безглузда, але розроблювачі формату домовилися вважати таку комбінацію нулем.

Тип double влаштований точно так само, як і float, різниця тільки в кількості розрядів і в тому, яке значення характеристики береться за нуль. Отже, маємо 11 розрядів для характеристики. За нуль береться значення 1023.

Дещо інакше влаштований тип long double. Крім відмінності у кількості байт для представлення, добавляється ще якісна : в мантисі явно вказується перший розряд. Тобто мантиса 1010… інтерпретується як 1.01 а не як 1.10., як це було у float чи double. Тому якщо 23бітна мантиса типу float забезпечує 24-знакову точність, 52-бітна мантиса double–53-бітну, то 64-бітна мантиса long double забезпечує 64-, а не 65-бітну точність.

Позначимо нормалізоване машинне представлення числа з плаваючою крапкою f у такий спосіб:

f M 2 p ,

де M - мантиса (1 M 2 ), 2 – основа системи числення, p - цілочи-

сельна характеристика.

Також позначимо 0 2 pmin , 2 pmax , де pmin , 2n - константи, що задають діапазон зміни характеристики.

Тоді можна записати, що 0 f . Кількість розрядів, що відводяться під мантису, обмежує відносну точність представлення чи-

152

2 pmin

сел у машині. Як характеристику цієї точності використовують величину 1 , визначену як найменше машинне число, результат додавання якого з числом 1 буде машинним числом, більшим за 1.

Нагадаємо, що для дійсних типів float, double кількість біт, що відводяться під характеристику рівні 8, 11 . При цьому зсув дорівнює відповідно на 127 і 1023 . Мантиса має сховану 1. Ці дані нам знадобляться для подальших обчислень.

При цьому величина порядку p=1...1 відведена під спеціальні (нечислові) значення.

Таким чином, найменші по модулю додатнє і від”ємне числа мають вид (трикрапкою зазначено, що подібне представлення має місце для двох розглянутих дійсних типів):

0 0...01 0...0

1 0...01 0...0

І 0 для дійсних типів float, double дорівнює відповідно 2 126 , 2 1022. Правіше від 0 розташовується множина точок, що слідують одна за одною із кроком n , де n - кількість біт для представлення мантиси конкретного дійсного типу. Чим правіше ми будемо розглядати числа, тим більше буде збільшуватися крок між ними. Наприкінці діапазону представлення відстань між двома сусідніми точками досягає

2 pmax n .

При цьому максимальні по модулю додатне і від”ємне числа мають вигляд:

 

 

 

 

 

0

 

1...10

 

1...1

 

 

 

 

 

 

 

 

1

 

1...10

 

1...1

 

 

 

 

 

і дорівнює відповідно 2128 , 21024.

Знайдемо значення 1 . Представлення числа 1 має вигляд:

 

 

 

 

 

 

 

 

float

 

0

 

01111111

 

...0

0

 

 

 

 

 

 

 

 

 

 

 

 

double

 

0

 

01111111111

 

0...

0

 

 

 

 

 

 

 

 

long double

 

0

 

011111111111111

 

0...

0

 

 

 

 

 

 

 

 

і, відповідно, найближче до 1 зверху машинне число

153

 

 

 

 

 

 

 

float

 

0

 

01111111

 

0...01

 

 

 

 

 

 

 

double

 

0

 

01111111111

 

0...01

 

 

 

 

 

 

 

long double

 

0

 

011111111111111

 

0...01

 

 

 

 

 

 

 

Таким чином, 1 дорівнює відповідно 2 24 , 2 53 та 2 64 .

Важливою характеристикою комп'ютера є співвідношення

0 , 1 , .

Визначення 1. Характеристики точності називаються збалансованими, якщо виконуються нерівності

 

0

2

,

 

2

 

1

 

1

Якщо ці умови не виконані, при реалізації обчислювальних задач необхідно використовувати спеціальні прийоми.

Описані особливості машинного представлення дійсних чисел приводять до того, що не для всіх його елементів вірні наступні співвідношення:

1.x 1 x ;

2.x x існує;

3.a (b c) (a b) c ; a b c c b a .

Наведемо кілька прикладів, що ілюструють властивості Приклад 1.

Показати, що 1 = 2 53 .

#include <stdio.h> main()

{

double x,y,z; x=1.0e-16; y=1.+(x+x); z=(1.+x)+x;

(y==z)?printf("\n!!Yes!!\n"): printf("\n!!No!!\n"); printf("y=%.16f", y);

printf("z=%.16f",z);

}

Приклад 2.

Написати програму для розв”язку квадратного рівняння для наступних значень коефіцієнтів: a 0.2e 45 , b c 1.

#include <stdio.h> #include<math.h> main()

{

154

double a,b,c,d,x1,x2; a=0.2e-45;

b=1.;

c=1.; d=b*b-4*a*c; if (d>0)

{

x1=(-b+sqrt(d))/(2*a); x2=(-b-sqrt(d))/(2*a); printf("x1=%e \nx2=%e\n",x1,x2);

printf("a*x1^2+b*x1+c=%e\n",a*x1*x1+b*x1+c);

printf("a*x2^2+b*x2+c=%e\n",a*x2*x2+b*x2+c);

}

else

if (d==0) {

x1=-b/(2*a); printf("x1=%e\n",x1);

}

else printf("no roots\n");

}

У результаті роботи програми ми одержимо, що

x1 0.00e 0 , x2 5.0e 45

і підставивши ці значення в квадратне рівняння, результат буде рівним 1.

ЗАВДАННЯ ДЛЯ САМОСТІЙНОЇ РОБОТИ.

1.Надрукувати число x таке, що x 1 x .

2.Надрукувати ціле число x таке, що x 1 x .

6.8КЕРУВАННЯ МАШИННИМ ПРЕДСТАВЛЕННЯМ ЧИСЕЛ.

Співпроцесор майже завжди виконує всі операції у форматі long

double. У співпроцесора є спеціальний керуючий двухбайтний регістр. Встановлення окремих бітів цього регістра диктує співпроцесору те чи інше поводження. Біти цього регістра відповідають за те, як будуть округлятися числа, як співпроцесор розуміє нескінченність. Розглянемо тільки два біти з цього слова – восьмий і дев'ятий. Саме вони визначають, як будуть оброблятися числа всередині співпроцесора.

Якщо восьмий біт містить одиницю (так встановлено за замовчуванням), то десять байт внутрішніх регістрів співпроцесора будуть використовуватися повністю, і ми одержимо повноцінний long

155

double. Якщо ж цей біт дорівнює нулю, то все визначається значенням 9 біта. Якщо він дорівнює одиниці, то використовуються 53 розряди мантиси (інші завжди дорівнюють нулю). Якщо ж цей біт дорівнює нулю – лише 24 розряди мантиси. Це збільшує швидкість обчислень, але зменшує точність. Іншими словами, точність роботи співпроцесора може бути знижена до типу double чи навіть float. Але це стосується тільки мантиси, порядок у будь-якому випадку буде містити 15 біт, так що діапазон типу long double зберігається в будьякому випадку.

Сучасні співпроцесори обробляють числа з такою швидкістю, що навряд чи в кого-небудь може виникнути необхідність у прискоренні за рахунок точності. Проте, це треба знати.

6.9 ОСОБЛИВОСТІ ВИКОНАННЯ АРИФМЕТИЧНИХ ОПЕРАЦІЙ.

Ми уже відзначали, що властивість повноти не виконується при машинному представленні дійсних чисел. Це випливає з того, що не кожен правильний дріб може бути представлена у виді скінченного дробу.

Наприклад, 1/9=0.11111..., 1/3=0.333333..., і т.д.

При роботі з такими числами використовується не точне, а наближене значення. Це потрібно враховувати при написанні коректних програм.

Двійкові дроби теж можуть бути нескінченними. Більше того, не будь-яке число, що виражається скінченним десятковим дробом, може бути також представлено скінченним двійковим дробом. Наприклад, число 1/5 представляється скінченним десятковим дробом як 0.2, але воно не може бути виражене скінченним двійковим дробом (але такі дроби завжди періодичні, оскільки раціональне число представляється скінченним або періодичним дробом у системі числення з будь-якою основою).

Розглянемо кілька прикладів некоректного використання дійсних типів.

Приклад 1.

#include<string.h>

#include<stdlib.h>

main()

{

float r; r=0.1;

printf("%.15e",r);

156

}

У результаті виконання програми ми побачимо 1.00000001490116e-001. Точність типу float – 7-8 десяткових розрядів, так що результат правильний, однак відкіля взялися інші цифри.

Виявляється число 0.1 не представляється у вигляді скінченного двійкового дробу, воно дорівнює 0.0(0011). І цей нескінченний двійковий дріб округляється на 24-ох знаках; ми одержуємо не 0.1, а деяке наближене число. Поекспериментувавши з різними числами, можна помітити, що точно представляються ті числа, що виражаються у вигляді m / 2n , де m, n – деякі цілі числа.

Приклад 2.

#include<string.h>

main()

{

float r; r=0.1;

if (r==0.1) printf("дорівнює\n"); else

printf("не дорівнює\n");

}

Після запуску програми ми одержимо «не дорівнює». Змінна R при присвоюванні одержує значення 0.100000001490116, тому що спочатку число 0.1 перетвориться до типу long double а потім, при присвоюванні, відбувається округлення до типу float. Процесори Intel працюють з 10-байтним типом long double, тому і ліва, і права частина рівності в умові спочатку перетвориться в цей тип, і лише потім виробляється порівняння. Те число, що зберігається в змінній r (помітьте вже не 0.1), представляється у вигляді скінченного двійкового дробу. При перетворенні цього числа в long double молодші, надлишкові в порівнянні з типом float розряди мантиси просто заповнюються нулями, і ми знову одержимо те ж саме число, тільки записане у форматі long double. А число 0.1 із правої частини рівності перетвориться в long double без проміжного перетворення в single і воно має нескінченне представлення в двійковому коді. Тому деякі з молодших розрядів мантиси будуть містити ненульові значення. Тому відбувається порівняння близьких, але не рівних чисел.

Якщо замінити число 0.1 на 0.5, то ми одержимо «Дорівнює». Тому що 0.5 – це скінченний двійковий дріб. При прямому приведенні її до типу extended у молодших розрядах будуть нулі. Точно такі ж

157

нулі виявляються в цих розрядах при перетворенні числа 0.5 типу float у тип long double. Тому в результаті ми порівнюємо два однакових числа.

Приклад 3. Розглянемо програму.

#include<string.h>

#include<stdlib.h>

main()

{

float r1; double r2; r1=0.1; r2=0.1;

if (r1==r2)

printf("дорівнює"); else

printf("не дорівнює");

}

Одержимо такий самий результат, тобто «не дорівнює» за тими самими причинами.

Приклад 4. Розглянемо наступну програму.

#include<stdio.h>

main()

{

float r; int i; r=1;

for (i=1;i<=10;i++) r-=0.1; printf("r=%e\n",r);

}

Після виконання програми екрані з'явиться -7.301569E-008. тому що число 0.1 не може бути передане точно в жодному з дійсних типів. При цьому постійно відбуваються округлення, і ці округлення призводять до того, що ми одержуємо в результаті не нуль, а «майже нуль».

Коли ми маємо справу з обчисленнями з обмеженою точністю, виникає такий парадокс. Нехай, наприклад, ми маємо точність до трьох значущих цифр. Додамо до числа 1.00 число 1.00*10-4. Якби усе було чесно, ми одержали б 1.0001. Але в нас обмежена точність, тому ми змушені округляти до трьох значущих цифр. У результаті виходить 1.00. Іншими словами, до деякого числа ми додаємо інше число,

158

більше нуля, а в результаті через обмежену точність ми одержуємо те ж саме число.

Визначення 1. Найменше додатне число, що при додаванні його до одиниці дає результат, який не дорівнює одиниці, називається машинним епсілон.

Приклад 6. Написати програму для обчислення машинного епсі-

лон.

#include<stdio.h>

main()

{

double r; r=1;

while (1+r/2>1) r=r/2; printf("%.20e",r);

}

У результаті буде отримане число 1.08420217248550e-19 для комп'ютерів класу Pentium III.

7 РЕАЛІЗАЦІЯ КОНЦЕПЦІЇ СТРУКТУРНОГО ПРОГРАМУВАННЯ В МОВІ С

Як вже вказувалось вище, процедурне програмування полягає у розбитті програми на окремі підпрограми. Такі підпрограми технологічно реалізуються у різних мовах програмування по-різному. В мові програмування С підпрограма записується у вигляді функції. Причому це єдиний механізм опису підпрограми, на відміну від інших мов програмування, як наприклад Pascal, де використовуються як функції так і процедури. Навіть головна програма, з якої починається процес виконання, записується у вигляді функції. Отже, розглянемо механізм використання функцій в мові С.

7.1ОГОЛОШЕННЯ ТА ВИЗНАЧЕННЯ ФУНКЦІЙ.

Смає дві основні синтаксичні схеми визначення функції:

а)

[<СПКП>] [<спеціалізація типу>] <описувач> (<список параметрів>)

[<список оголошень параметрів>] {<тіло>}

Приклад:

159

static void PM21 (s) char *s;

{printf(“is good students%s”,s);}

б)

[<СПКП>] [<спеціалізація типу>] <описувач> (<список типів параметрів>){<тіло>} Приклад:

extern float f (int a; double b;)

{

…}

Функція може повертати довільне значення, крім масиву та функції (може повернути вказівник на масив та функцію), тобто може повертати структури, елементи перелічувального типу тощо. Специфіка є у визначенні функції, що повертає значення типу int. Функції, що повертають значення типу int, не обов'язково попередньо оголошувати до їх використання. Момент першого виклику такої функції інтерпретується компілятором, як оголошення з типом результуючого значення int. Значення, що повертається функцією, генерується при виконанні оператора return. При цьому обчислення значення відповідного виразу приводиться до типу результуючого значення і повертається в точку виклику функції. Якщо оператор return присутній, але ніяке значення не вказується, поведінка програми не визначена:

typedef struct { char name [10]; int d;} people;

people SORT (people a, people b);

{

return ((a.d>b.d)?a:b); };

7.2 ФОРМАЛЬНІ ТА ФАКТИЧНІ ПАРАМЕТРИ.

Існує дві форми визначення списку формальних параметрів (див. попер. пункт).

Необхідно враховувати, що при виклиці функції відбувається приведення типів фактичних параметрів до типів формальних згідно з правилами перетворення типів.

В С існує механізм передачі фактичних параметрів по значенню. Це означає, що при виконанні функції утворюються локальні копії всіх фактичних параметрів, що передаються в функцію. Для ілюстрації специфіки механізму передачі параметрів по значенню напишемо функцію, що міняє місцями дві змінні:

160

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]