Delphi Math: Защо е 0,7‹0,70?

Ако имам променливи a, b, an c от тип double, нека c:=a/b и дам стойности на a и b от 7 и 10, тогава стойността на c от 0,7 се регистрира като ПО-МАЛКА ОТ 0,70.

От друга страна, ако всички променливи са с разширен тип, тогава стойността на c от 0,7 не се регистрира като по-малка от 0,70.

Това изглежда странно. Каква информация ми липсва?


person Al C    schedule 02.05.2010    source източник
comment
възможен дубликат на stackoverflow.com/questions/1661273/   -  person Michael Mrozek    schedule 02.05.2010
comment
Това е един от най-често срещаните (най-често срещаните?) Въпроси за SO и няма нищо общо с Delphi, а с начина, по който компютрите съхраняват числа с плаваща запетая.   -  person Andreas Rejbrand    schedule 02.05.2010
comment
Не че това го спира да бъде гласуван за :)   -  person Michael Mrozek    schedule 02.05.2010
comment
възможен дубликат на stackoverflow.com/ въпроси/1193554/   -  person Ralph M. Rickenbach    schedule 03.05.2010
comment
Благодаря на всички за отговорите. Ларс -- Приех отговор, въпреки че всички те работиха доста добре от моя гледна точка (...както и отговорите на подобни въпроси ;-)   -  person Al C    schedule 04.05.2010


Отговори (4)


Няма представяне на математическото число 0.7 в двоична форма с плаваща запетая. Вашето изявление изчислява в c най-близкото double, което (според това, което казвате, не съм проверил) е малко под 0,7.

Очевидно при разширена точност най-близкото число с плаваща запетая до 0,7 е малко над него. Но все още няма точно представяне за 0,7. Няма никаква точност в двоичните числа с плаваща запетая.

Като основно правило всяко нецяло число, чийто последен ненулев десетичен знак не е 5, не може да бъде представено точно като двоично число с плаваща запетая (обратното не е вярно: 0,05 също не може да бъде представено точно).

person Pascal Cuoq    schedule 02.05.2010

Първо, трябва да се отбележи, че плаващите литерали в Delphi са от разширен тип. Така че, когато сравнявате двоен с литерал, двойният вероятно първо се „разгръща“ до Разширен и след това се сравнява. (Редактиране: Това е вярно само в 32-битово приложение. В 64-битово приложение Extended е псевдоним на Double)

Тук ще се покажат всички ShowMessage.

procedure DoSomething;
var
  A, B : Double;
begin
  A := 7/10;
  B := 0.7; //Here, we lower the precision of "0.7" to double

  //Here, A is expanded to Extended...  But it has already lost precision. This is (kind of) similar to doing Round(0.7) <> 0.7
  if A <> 0.7 then 
    ShowMessage('Weird');

  if A = B then //Here it would work correctly.
    ShowMessage('Ok...');

  //Still... the best way to go...
  if SameValue(A, 0.7, 0.0001) then
    ShowMessage('That will never fails you');
end;

Ето малко литература за вас

Какво всеки компютърен специалист трябва да знае за аритметиката с плаваща запетая

person Ken Bourassa    schedule 02.05.2010
comment
+1 за каноничната връзка относно аритметиката с плаваща запетая. - person mbauman; 02.05.2010

Това е свързано с броя на цифрите с точност в двата различни типа с плаваща запетая, които използвате, и факта, че много числа не могат да бъдат представени точно, независимо от точността. (От чисто математическа гледна точка: ирационалните числа са повече от рационалните)

Вземете например 2/3. Не може да бъде представен точно в десетичен знак. С 4 значещи цифри, това ще бъде представено като 0,6667. С 8 значещи цифри ще бъде 0,66666667. Завършващото 7 е обобщение, което отразява, че следващата цифра би била > 5, ако имаше място да я запазите.

0,6667 е по-голямо от 0,66666667, така че компютърът ще оцени 2/3 (4 цифри) > 2/3 (8 цифри).

Същото важи и за вашите .7 срещу .70 в двойни и разширени варианти.

За да избегнете този конкретен проблем, опитайте се да използвате един и същ цифров тип във вашия код. Когато работите с числа с плаваща запетая като цяло, има много малки неща, за които трябва да внимавате. Най-важното е да не пишете кода си, за да сравнявате две плаващи стойности за равенство - дори и да имат една и съща стойност, има много фактори в изчисленията, които могат да ги направят съвсем малко различни. Вместо да сравнявате за равенство, трябва да проверите дали разликата между двете числа е много малка. Колко малка трябва да бъде разликата зависи от вас и от естеството на вашите изчисления и тя обикновено се нарича епсилон, взета от теоремата на смятането и доказателството.

person dthorpe    schedule 02.05.2010
comment
Всъщност всички изчислими числа могат да бъдат представени точно :) - person fishlips; 04.05.2010
comment
Мисля, че това е разумно обяснение, но бих добавил, че много числа, които имат рационално завършващо десетично представяне, нямат прецизно завършващо (рационално) двоично представяне с плаваща запетая. Този факт изглежда убягва от вниманието на много разработчици. - person Warren P; 09.12.2010

Пропускате Това нещо.

Вижте специално главата „Проблеми с точността“. Вижте също отговора на Паскал. За да коригирате кода си, без да използвате типа Extended, трябва да добавите единицата Math и да използвате SameValue функция от там, която е специално създадена за тази цел.

Уверете се, че използвате Epsilon стойност, различна от 0, когато използвате SameValue във вашия случай.

Например:

var
  a, b, c: double;


begin
  a:=7; b:=10;
  c:=a/b;

  if SameValue(c, 0.70, 0.001) then
    ShowMessage('Ok')
  else
    ShowMessage('Wrong!');
end;

HTH

person John Thomas    schedule 02.05.2010