Каков наилучший способ избежать неожиданного поведения из-за двоичного хранения чисел с плавающей запятой? - PullRequest
4 голосов
/ 16 февраля 2010

Недавно я писал простой цикл for и получил неожиданное поведение:

for(double x = 0.0; x <= 1.0; x += 0.05)
{
    Console.WriteLine(x.ToString());
}

Это вывод:

0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
0.45
0.5
0.55
0.6
0.65
0.7
0.75
0.8
0.85
0.9
0.95

Обратите внимание, что 1 не появляется, хотя условие для продолжения цикла for, кажется, включает его. Я понимаю, что причина этого в том, что десятичные числа хранятся в памяти в двоичном виде, , т.е. 1 не совсем точно 1, а фактически 1.0000000000000002 (согласно переменной watch в Visual Studio). Итак, мой вопрос: как лучше всего избежать этого неожиданного поведения? Одним из способов было бы использование типа decimal вместо double, но большинство функций System.Math работают только на double s, и приведение между ними не является простым.

Ответы [ 4 ]

9 голосов
/ 16 февраля 2010

Не проверяйте двойные числа на равенство.

Здесь вы можете использовать целочисленную арифметику вместо:

for (int i = 0; i <= 20; ++i)
{
    double x = (double)i / 20.0;
    Console.WriteLine(x);
}

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

2 голосов
/ 16 февраля 2010

всегда управляйте округлением, не позволяйте системе справиться с этим

  for(double x = 0.0; Math.Round(x,2,MidpointRounding.AwayFromZero) <= 1.0; x += 0.05)
  {
      Console.WriteLine(x.ToString());
  }

выдаст

0
0,05
0,1
0,15
0,2
0.25
0,3
0,35
0,4
0,45
0,5
0,55
0,6
0,65
0,7
0,75
0,8
0,85
0,9 * * тысяча двадцать-пять 0,95
1

1 голос
/ 16 февраля 2010

Это называется ошибкой с плавающей точкой, и о ней написано много. Одним из решений для вашего случая было бы определение ошибки, такой как

err = .000001

и сравните с ошибкой 1,0.

0 голосов
/ 16 февраля 2010

вы не можете масштабировать и использовать int

 for(int ix=0; ix < 1000; ix += 50)
 {

  }

затем разделите на 1000, если вам нужно. но таким образом вы получите точное поведение цикла

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...