Delphi Math: Почему 0,7 <0,70?

Если у меня есть переменные a, b, a 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/ questions / 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 цифр).

То же самое и с вашими 0,7 против 0,7 в двойных и расширенных переменных.

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

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