Является ли это действительным сравнением с плавающей запятой, которое учитывает определенное количество десятичных знаков? - PullRequest
9 голосов
/ 07 февраля 2012

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

/// <summary>
/// Determines if the float value is equal to (==) the float parameter according to the defined precision.
/// </summary>
/// <param name="float1">The float1.</param>
/// <param name="float2">The float2.</param>
/// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
/// <returns></returns>
public static bool AlmostEquals(this float float1, float float2, int precision = 2)
{
    return (Math.Round(float1 - float2, precision) == 0);
}

Примечание: Я ищу сравнение по десятичным разрядам, а не допуск.Я не хочу, чтобы 1 000 000 равнялся 1 000 001.

Ответы [ 2 ]

2 голосов
/ 07 февраля 2012

На основе ответа @ infact и некоторых комментариев в вопросе и ответе, который я придумал

public static bool AlmostEquals(this float float1, float float2, int precision = 2) 
{ 
    float epsilon = Math.Pow(10.0, -precision) 
    return (Math.Abs(float1-float2) <= epsilon);   
} 

Это позволяет принимать любое целое число, вы можете проверить точность> 1,0, используя точность= -x, где x - это степень 10, с которой сравнивать.

Я бы также порекомендовал сделать точность по умолчанию = 3, что даст вам точность до десятой доли пенни по умолчанию, если этоМетод использовался для учета.

0 голосов
/ 09 февраля 2012

Если пользователь хочет «сравнить два числа с плавающей точкой, используя заданное количество десятичных знаков (значащих цифр)», и это фактически означает, что у нас есть функция

Почти эквалайзеры (14.3XXXXXXXX, 14.3YYYYYYY, 1) == true для всех возможных XXX и YYY и последний параметр - это десятичное число после десятичной точки.

есть простой, но неудачный ответ:

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

Приведенные здесь решения уже ломаются с Почти Эквивалентами (0.06f, 0.14f, 1) = true, но 0! = 1.

Почему? Первая причина - чрезвычайная чувствительность. Например: 0.0999999999 .... и 0.100000 ... 1 Во-первых, у них разные цифры, но в разнице они почти неразличимы, они практически точно совпадают. Что бы ни делала мифическая функция, она не может допустить даже небольших различий в вычислениях.

Вторая причина в том, что мы хотим на самом деле рассчитывать с числами. Я использовал VC 2008 с C #, чтобы распечатать правильные значения функции Math.pow. Первый - это параметр точности, второй - шестнадцатеричное значение результирующего числа с плавающей запятой, а третий - точное десятичное значение.

1 3dcccccd 0.100000001490116119384765625

2 3c23d70a 0,00999999977648258209228515625

3 3a83126f 0,001000000047497451305389404296875

4 38d1b717 0,0000999999974737875163555145263671875

5 3727c5ac 0,00000999999974737875163555145263671875

6 358637bd 9.999999974752427078783512115478515625E-7

Как видите, последовательность 0.1, 0.01, 0.001 и т. Д. Дает числа, которые являются превосходными приближениями, но либо немного слишком малы, либо слишком велики.

Что если мы обеспечим, чтобы в данном месте была правильная цифра? Давайте перечислим 16 двоичных значений для 4 битов

0.0
0.0625
0.125
0.1875
0.25
0.3125
0.375
0.4375
0.5
0.5625
0.625
0.6875
0.75
0.8125
0.875
0.9375

16 различных двоичных чисел должно быть достаточно для десяти десятичных чисел, если мы хотим вычислять только с одним местом после десятичной точки. В то время как 0,5 в точности равняется, принудительное использование одной и той же десятичной цифры означает, что 0,4 требуется 0,4375, а 0,9 - 0,9375, что приводит к серьезным ошибкам.

Нарушение первого условия чрезвычайной чувствительности означает, что вы не можете сделать ничего разумного с такими числами. Если вы знаете, что десятичное число имеет определенное значение, вам не нужно вычислять в первую очередь.

Документация C # даже приводит пример: http://msdn.microsoft.com/en-us/library/75ks3aby.aspx

Примечания для абонентов

Из-за потери точности, которая может возникнуть в результате представления десятичные значения как числа с плавающей запятой или арифметика операции со значениями с плавающей точкой, в некоторых случаях Round (Double, Метод Int32) может не отображаться для округления значений средней точки до ближайшего четное значение в десятичной позиции цифр. Это иллюстрируется в следующий пример, где 2,135 округляется до 2,13 вместо 2,14. Это происходит потому, что внутри метода метод умножает значение на 10 цифр, и операция умножения в этом случае страдает от потеря точности.

...