2013-07-27 5 views
1

У меня проблема с вычислением простого арифметического уравнения с использованием переменных двойной точности.Неверный двойной расчет точности Delphi

У меня есть компонент, который имеет свойство Value, которое двойная точность, и я устанавливаю это свойство 100.

Тогда я делаю простое вычитание, чтобы проверить, если это значение действительно 100:

var 
check: double; 
begin 
    check:= 100 - MyComponent.Value 
    showmessage(floattostr(check)); 
end; 

проблема заключается в том, что я не получаю ноль, я получаю -1.4210854715202E-14, которая является проблемой, потому что программа проверяет, является ли этот результат точно равен нулю

любая идея, как решить эту проблему?

+3

Что каждый программист должен знать о арифметики с плавающей точкой HTTP: // Http: //floating-point-gui.de/ И вы видели Http: //stackoverflow.com/questions/6106119/how-to-compare-double-in-delphi и, например, http://docwiki.embarcadero.com/Libraries/XE4/en/System.Math.IsZero –

ответ

5

Хотя вы заявляете обратное, значение, возвращаемое MyComponent.Value, явно не соответствует 100. Если бы это было так, то 100 - MyComponent.Value было бы точно равно 0. Можно сказать, что потому, что 100 точно представим в двоичной с плавающей запятой.

Легко видеть, что 100.0 - 100.0 = 0.0.

var 
    x, y: Double; 
.... 
x := 100.0; 
y := 100.0; 
Assert(x-y=0.0); 

Вы найдете в вашем сценарии, что

MyComponent.Value = 100.0 

вычисляется как False.

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

Я полагаю, что MyComponent.Value фактически выполняет арифметику, а не, как вы утверждаете, возвращает 100.0.

Иногда лучший способ проверить равенство с значениями с плавающей запятой - проверить приблизительное равенство. Например, abs(x-y)<tol, где tol - некоторое небольшое число. Трудность заключается в том, что может быть трудно найти надежный выбор tol.

Точно как вы должны выполнить этот тест, трудно сказать, не зная больше деталей.

+0

Значение из MyComponent.Value точно 100. Я также напечатал это значение, используя: showmessage (FormatFloat ('#. ### #### E - ## ', MyComponent.Value). И я получил 1E2. Но когда я делаю вычитание, у меня есть что-то еще ... Но ваша идея толерантности хорошая, я тоже об этом, но Я хотел бы понять, почему это происходит, и если есть более разумный способ решить эту проблему, но, учитывая неопределенность работы с плавающей запятой, я предполагаю, что я должен использовать эту идею толерантности – Felipe

+2

№ 100% точно, значение не 100 Если бы это было так, MyComponent.Value = 100 оценил бы True, а это не будет.Когда вы использовали FormatFloat, вы округлились до небольшого числа значимых цифр. –

+2

Если вы все еще не верите, попробуйте следующее: 'ShowMessage (FormatFloat ('#. ####### E - ##', 99.999999999)) ' –

3

Предлагайте вам никогда не проверять нуль простым вычитанием, когда одно из чисел является поплавком. Вместо этого используйте функцию IsZero с любой необходимой точностью (например, 0.00001) для параметра Epsilon.

procedure CheckFor100(MyPrecision); 
begin 
    if IsZero(100 - MyComponent.Value, MyPrecision) then 
    ShowMessage('MyComponent value was 100') 
    else 
    ShowMessage('MyComponent value was not 100'); 
end; 

В качестве альтернативы вы можете рассмотреть функцию SameValue. Я думаю, что оба IsZero и SameValue находятся в модуле Math.

4

При использовании чисел с плавающей запятой вы никогда не должны выполнять точные сравнения; всегда используйте небольшое значение Epsilon, как описано в The Floating-Point Guide - What Every Programmer Should Know.

Примечание: Я намеренно заявляю об этом в абсолютном выражении. Конечно, являются исключениями, но на практике они обычно являются исключительными.Of Конечно, вы можете быть в ситуации, когда ваш проблемный домен исключительный, и гарантирует точное сравнение. В подавляющем большинстве кода , который я поддерживал, это не так.

Math блок (он был с Delphi в течение долгого долгого времени) содержит следующие функции, которые обрабатывают Epsilon значения для вас.

Когда вы передаете Epsilon из нуля (то есть 0.0) или вообще не имеете значения для функций ниже, они будут оценивать разумное значение с использованием этих констант.

Примечание:

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

const 
    FuzzFactor = 1000; 
    SingleResolution = 1E-7 * FuzzFactor; 
    DoubleResolution = 1E-15 * FuzzFactor; 
{$IFDEF EXTENDEDIS10BYTES} 
    ExtendedResolution = 1E-19 * FuzzFactor; 
{$ELSE EXTENDEDIS10BYTES} 
    ExtendedResolution = DoubleResolution; 
{$ENDIF EXTENDEDIS10BYTES} 

Функции:

function CompareValue(const A, B: Extended; Epsilon: Extended = 0): TValueRelationship; overload; 
function CompareValue(const A, B: Double; Epsilon: Double = 0): TValueRelationship; overload; 
function CompareValue(const A, B: Single; Epsilon: Single = 0): TValueRelationship; overload; 

function SameValue(const A, B: Extended; Epsilon: Extended = 0): Boolean; overload; 
function SameValue(const A, B: Double; Epsilon: Double = 0): Boolean; overload; 
function SameValue(const A, B: Single; Epsilon: Single = 0): Boolean; overload; 

function IsZero(const A: Extended; Epsilon: Extended = 0): Boolean; overload; 
function IsZero(const A: Double; Epsilon: Double = 0): Boolean; overload; 
function IsZero(const A: Single; Epsilon: Single = 0): Boolean; overload; 
+0

Существует много раз, когда он способен точно сравнивать значения с плавающей запятой. И времена, когда это плохая идея. Как вы заявляете, это не так ясно. –

+0

@DavidHeffernan см. Редактировать. Лучше? –

+0

В моем сценарии я сравниваю введенные пользователем данные с другими введенными пользователем данными. Поэтому у меня может быть свойство setter, и я бы написал 'if NewValue <> OldValue'. Или у меня может быть оптимизация memoisation или кеширования, для которой точное сравнение входных параметров было бы подходящим. –

Смежные вопросы