2009-09-08 3 views
5

Я проверял этот код из Brainteasers:Не была ли точность двойного типа 15 цифр в C#?

 double d1 = 1.000001; 

     double d2 = 0.000001; 

     Console.WriteLine((d1 - d2) == 1.0); 

И результат "False". Когда я меняю тип данных:

 decimal d1 = 1.000001M; 

     decimal d2 = 0.000001M; 

     decimal d3 = d1-d2; 

     Console.WriteLine(d3 == 1); 

Программа пишет правильный ответ: «Истина».

Эта проблема просто использует 6 цифр после с плавающей запятой. Что случилось с точностью до 15 цифр?

ответ

28

Это не имеет ничего общего с точностью - это связано с ошибками репрезентативного округления.

System.Decimal способен представлять большие числа с плавающей запятой со значительно меньшим риском возникновения любых ошибок округления, подобных тому, который вы видите. System.Single и System.Double не способны на это и будут округлять эти числа и создавать проблемы, подобные тем, которые вы видите в своем примере.

System.Decimal использует коэффициент масштабирования для удержания позиции десятичной точки, таким образом, что позволяет для точного представления заданного числа с плавающей точкой, в то время как System.Single и System.Double только приблизительным значение как можно лучше.

Для получения дополнительной информации, пожалуйста, см System.Double:

Помните, что число с плавающей точкой можно аппроксимировать только десятичное число, и что точность числа в с плавающей точкой определяет, как точно, что число приближается к десятичному числу . По умолчанию значение Double содержит 15 десятичных цифр с точностью , хотя не более 17 цифр поддерживается внутри страны. точности с плавающей запятой числа имеет несколько последствий:

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

  • Математической или сравнение операции, которая использует с плавающей точкой числа может не дать тот же результат, если десятичного числа используется, потому что число с плавающей точкой не может точно аппроксимировать десятичное числа .

+0

http://msdn.microsoft.com/en-us/library/system.decimal.aspx «Десятичный тип не устраняет необходимость округления. Скорее, он минимизирует ошибки из-за округления. Например, следующее код производит результат 0.999999999999999999999999999999, а не 1. " –

+0

@quant_dev: достаточно хорошо, я отредактировал свой ответ, чтобы отразить это :) –

+0

«System.Decimal использует коэффициент масштабирования, чтобы удерживать позицию десятичной точки, что позволяет точно представлять заданное число с плавающей запятой» - это не соответствует действительности; например, 1/3 не будет отображаться точно ни с помощью двойников (которые используют двоичную базу), ни десятичных знаков (которые используют десятичную базу). Фактически единственной разницей между Double и Decimal является база, количество бит, зарезервированных для показателя и значимости, и может ли экспонента изменять знак (да для Double, no for Decimal). Кроме того, они похожи. System.Decimal не имеет произвольной точности! –

3

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

2

Избегайте сравнения относительно равенства чисел с плавающей запятой.

3

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

В этом случае .1 десятичный знак повторяет навсегда, если представлен в двоичном формате. Он преобразуется в .000110011001100110011 ... и повторяется навсегда. Никакая точность не сохранит это точно.

8

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

if (Math.Abs(d1 - d2) <= Double.Epsilon) ... 

Это тесты, чтобы увидеть, если d1 и d2 представлены одним и тем же битов дают или взять значащий бит.

Коррекция(Добавлено 2 Mar 2015)

При дальнейшем рассмотрении, код должен быть больше, как это:

// Assumes that d1 and d2 are not both zero 
if (Math.Abs(d1 - d2)/Math.Max(Math.Abs(d1), Math.Abs(d2)) <= Double.Epsilon) ... 

Другими словами, взять абсолютное различие между d1 и d2 , затем масштабируйте его на самый большой из d1 и d2, а затем сравните его с Epsilon.

Список литература
http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx
http://msdn.microsoft.com/en-us/library/system.double.aspx#Precision

+0

В вашей ссылке вы можете прочитать, что Epsilon является наименьшим положительным Double и не эквивалентен эпсилону машины, который представляет верхнюю границу относительного ошибка из-за округления в арифметике с плавающей запятой. Итак, почему ваш ответ противоречит ссылке, которую вы опубликовали? – Nicolas

4

В десятичных орудия типа десятичного с плавающей точкой, тогда как двойного является двоичной плавающей точкой.

Преимущество десятичной системы состоит в том, что она ведет себя как человек в отношении округления, и если вы инициализируете ее десятичным значением, тогда это значение сохраняется точно, как вы указали. Это справедливо только для десятичных чисел конечной длины и внутри представимого диапазона и точности. Если вы инициализировали его с помощью 1.0M/3.0M, тогда он не будет храниться точно так же, как вы бы написали 0,333333, повторяющийся на бумаге.

Если вы инициализируете двоичное значение FP с десятичной запятой, оно будет преобразовано из понятной для человека десятичной формы в двоичное представление, которое редко будет точно таким же значением.

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

Обратите внимание, что в два раза с точностью до приблизительно 15 значащих цифр не 15 знаков после запятой.d1 инициализируется значением 7 значащих цифр не 6, а d2 имеет только 1 значащую цифру. Тот факт, что они имеют существенно различную величину, тоже не помогает.

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