salehne Ответов: 5

Проблема с плавающими числами


Привет,
Взгляните на этот код C#:
double f = 10.123 - 10.0;
Console.WriteLine(f);

Результат должен быть "0.123", но здесь это "0.122999999999999"!!
В чем проблема??
Я знаю, что разница в Эпсилоне, но что, если я хотел бы получить реальный результат?

Заранее спасибо!

5 Ответов

Рейтинг:
39

CPallini

Вы не можете получить реальный результат: - Что Каждый Компьютерщик Должен Знать Об Арифметике С Плавающей Запятой?"[^].

В качестве обходного пути вы можете использовать округление, предусмотренное для выходного представления:

double f = 10.123 - 10.0;
Console.WriteLine(f.ToString("0.###"));


Kornfeld Eliyahu Peter

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

Рейтинг:
29

Andreas Gieriet

Это неотъемлемое свойство компьютерной арифметики с плавающей запятой, то есть арифметики с ограниченной точностью.

В качестве примера возьмем число 1/7 = 0.142857142857142857142857...
Если вы теперь вычисляете 7x(1/7) с ограниченной точностью (например, с помощью бумаги и карандаша), вы видите тот же эффект:

точность 1/7 7х(1/7) округление до 2 цифр
0 0 0 0.00
1 0.1 0.7 0.70
2 0.14 0.98 0.98
3 0.142 0.994 0.99
4 0.1428 0.9996 1.00
5 0.14285 0.99995 1.00
6 0.142857 0.999999 1.00
... ... ...
12 0.142857142857 0.999999999999 1.00
... ... ... ...


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

  1. Определитесь с точностью отображения чисел (например, физическая длина, заданная в метрах [м] и точная до 1 миллиметра [мм] - точность отображения = 3-округление до 3 цифр дроби при отображении). например. d.ToString("F3").
  2. Определите дельту сравнения, используемую для операций равенства и отношения (например,==, !=, <, <=, >=, >). например, точность отображения + 1 --> см. Демистификация операций равенства и отношений с плавающей запятой в C# [^]. Например. var epsilon = new RealExtension.Epsilon(1E-4);...if (x.EQ(y, epsilon)) ...
  3. Не использовать GetHashCode() о числах с плавающей запятой, ни прямо, ни косвенно, например, как ключ в словарях и хэш-коллекциях (из-за неточной природы чисел с плавающей запятой).


Овации
Энди

PS: Не бросайте вперед и назад между числовыми доменами. Это плохая привычка, которая стоит вам больше в долгосрочной перспективе, чем помогает. Определитесь с доменом и оставайтесь в нем. Single имеет ту же проблему, что и заявленная выше, ее просто перекладывают в другие места.


BillWoodruff

+5 очень исчерпывающий ответ.

Andreas Gieriet

Спасибо за ваши 5!
Овации
Энди

Рейтинг:
2

Kornfeld Eliyahu Peter

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

http://msdn.microsoft.com/en-us/library/system.double.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-7[^]

Когда вы пишете эту строку
double f = 10.123 - 10.0;

это то же самое что писать
double f = 10.123d - 10.0d;

поскольку C# по умолчанию имеет двойную точность с плавающей запятой...
Нет способа получить точное значение, но вы можете приблизиться (или продвинуться) к нему...Все, что вам нужно сделать, это решить, какая точность вам нужна...
например, изменив свою линию на
double f = 10.123f - 10.0f

вы получите результат 0.12300014495849609...
Как вы можете видеть в нижней строке, вам придется играть с округлением вверх/вниз при использовании чисел с плавающей запятой...

Однако вам повезло - эта проблема с плавающей запятой была известна Microsoft, и есть другой тип, который можно использовать для вычислений с плавающей запятой - десятичный[^]...
У него есть свои недостатки (читайте страницу MSDN), но для вашего диапазона (в зависимости от вашего случая) он даст правильный ответ. попробуйте...
decimal f = 10.123m - 10.0m;


BillWoodruff

+5 ясно, точно и показывает технику, которая решает проблему. Кто бы ни проголосовал за это "а #1", его голова должна быть осмотрена.

Kornfeld Eliyahu Peter

Спасибо!
(На самом деле я разместил его в два этапа - некоторые технические проблемы - и первая версия содержала только цитату MSDN...Так что это может многое объяснить...)

Рейтинг:
2

NewPast

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

single f = 10.123 - 10.0;