W&PBBS - Проблема із заокругленням у Delphi
Народ, допоможіть, плиз!
procedure TForm1.Button1Click(Sender: TObject); Var des1: integer; p,r: real; begin r := 30.45; p := frac(r)*60; des1 := round(int(p)); MessageDLG(inttostr(des1),mtInformation,[mbOk],0); end;
У мене ця програма видає повідомлення з текстом "26". Якщо не помиляюся, то 0,45*60=27. За ідеєю, round(int(27))=27. Чи мене глючить? Делфі в мене сьома.
Scandinavian: Коли ти робиш int(p) значення p обрізається до int без округлення, просто відкидається дрібна частина. Тобто до округлення там було щось на кшталт 26.99999999999999 після int(p) вийшло 26, відповідно round(26) повертає 26.
Scandinavian пише: Якщо я не помиляюся, то 0,45 * 60 = 27 Тут ти помиляєшся. Справа в тому, що числа з точкою, що плаває, видаються з обмеженою точністю. Наприклад, під тип Extended відведено 10 байт. За допомогою 10 байт можна уявити 256^10 різних чисел, тобто близько 10^24. Однак максимальне значення для Extended судячи з хелпу 1.18 x 10^4932. Як бачимо, це набагато більше. Тому не будь-яке число можна уявити з абсолютною точністю. Відповідно 0.45*60.0 не обов'язково дорівнюватиме 27.0.
Я трохи додав у попередній пост, поки редагував з'явилася твоя відповідь.
А що саме видається нелогічним? І навіщо ти перед округленням відокремлюєш цілу частину числа, адже округлення і так залишить цілу частину?
Scandinavian: Ось давай розберемося з твоїм кодом. Для початку наведемо опис стандартних функцій Делфі:
функція Int(X: Extended): Extended; функція Round(X: Extended): Int64; функція Trunc(X: Extended): Int64;
Звідси ми бачимо чому тобі довелося використати нагромадження
Усі абсолютно правильно помітили,що воно явно надмірне. Але якщо ми приберемо функцію round, то отримаємо несумісність типів змінної des1 і результату виразу праворуч оператора присвоювання. Тобто. Integer = Extended. Звичайно Делфі з цим ні за що не погодиться, тому і з'явилася функція round, яка в даному випадку просто забезпечує явне перетворення типів. Насправді все дуже легко дозволяється з використанням функції Trunc. Вона як і Int просто відкидає дрібну частину, але результат при цьому буде цілого типу, що тобі і потрібно в даному випадку. Отже, має бути так:
І має рацію Paul, коли говорить про помилки представлення чисел у ПК, зумовлених кінцівкою розрядної сітки та деякими іншими факторами. Це не нонсенс, це цілком реально, коли 2,0 * 2,0 = 3,9999999999999999999999999999. Ну може трохи перебільшено :D І в нашому випадку на жаль 0,45 * 60 = 26.999999999999957 а не 27,00 як слід зі звичайної арифметики. Але як прозорливо зауважив Зорро, в даному випадку ця проблема "лікується" використанням більш потужного типу - Extended. У результаті приходимо до наступного коду:
procedure TForm1.Button1Click(Sender: TObject); Var des1: integer; p,r: Extended; begin r := 30.45; p := frac(r)*60; des1: = Trunc (p); MessageDLG(inttostr(des1),mtInformation,[mbOk],0); end;
Дякуємо всім за допомогу! Я завжди думала чомусь, що в "комп'ютері" достатньо сили, щоб правильно порахувати. Тепер ситуація прояснилася:). Єдино, моя проблема не зовсім вирішилася, т.к. той код, який я привела у першому повідомленні, відрізняється від справжнього, де змінна r береться з бд через ADO. Я змінила у процедурі все на extended.
procedure Test(r: extended); Var des1: integer; p: Extended; begin p:= frac(r)*60; des1: = Trunc (p); MessageDLG(inttostr(des1),mtInformation,[mbOk],0); end; Test(RemoteADOQuery.Fields[LATITUDE].AsFloat);
При LATITUDE=30.45 отримую 26. Чи може залежати від типу поля 'LATITUDE' у бд? Заздалегідь дякую всім!
Paul, дякую!
Ось так ось дружище Зорро :D :D :D