Цикл for в C++ с использованием двойного разрыва на один шаг раньше, граничное значение не достигнуто

У меня есть простая программа на C++, скомпилированная с использованием gcc 4.2.4 на 32-битной Ubuntu 8.04. Он имеет цикл for, в котором переменная double увеличивается от нуля до единицы с определенным размером шага. Когда размер шага равен 0.1, поведение соответствует ожидаемому. Но когда размер шага равен «0,05», цикл завершается через 0.95. Кто-нибудь может сказать мне, почему это происходит? Вывод соответствует приведенному ниже исходному коду.

#include <iostream>

using namespace std;

int main()
{
    double rangeMin = 0.0;
    double rangeMax = 1.0;
    double stepSize = 0.1;

    for (double index = rangeMin; index <= rangeMax; index+= stepSize)
    {
        cout << index << endl;
    }
    cout << endl; 

    stepSize = 0.05;
    for (double index = rangeMin; index <= rangeMax; index+= stepSize)
    {
        cout << index << endl;
    }

    return 0;
}

ВЫХОД

sarva@savija-dev:~/code/scratch$ ./a.out 
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1

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
sarva@savija-dev:~/code/scratch$

person Community    schedule 17.08.2009    source источник
comment
может быть проблема с плавающей запятой, я должен проверить!!!   -  person Ahmed Said    schedule 17.08.2009
comment
Не принимайте ответы сразу! Дайте людям время, чтобы добраться до вашего вопроса и ввести ответ.   -  person GManNickG    schedule 17.08.2009


Ответы (10)


При использовании значений с плавающей запятой не каждое значение может быть точно представлено, 0.95+0.05 > 1, потому что 0.95 не может быть точно представлено значением double.

Посмотрите, что Википедия говорит о точности с плавающей запятой.

Если вы посмотрите на конвертер IEEE с плавающей запятой, вы увидите, что значение из 0.95 в 64-битном формате с плавающей запятой (double) равно 0-01111111110-1110011001100110011001100110011001100110011001100110, если ввести его в калькулятор с плавающей запятой вы получаете значение 0.95000016, и добавление 0.05 к этому приводит к отметке 1.0.

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

person Motti    schedule 17.08.2009
comment
В этом конкретном примере лучшим решением может быть вычисление index как целого числа и сравнение index * stepSize. - person Matthew Scharley; 17.08.2009

Как правило, когда вы сравниваете двойники, простого сравнения недостаточно, и вы должны сравнивать их «с точностью до точности». то есть:

if ( fabs(double1-double2) < 0.0000001 ) {
  do-something
}

Проблема возникает из-за представления переменных типа double.

person Anna    schedule 17.08.2009

Вы не должны использовать == или ‹= для двойных значений из-за их внутреннего представления. На последнем шаге вы получите 0.95000000000000029. Вместо этого вы можете использовать следующий код:

stepSize = 0.05;
// stepSize/2 looks like a good delta for most cases
for (double index = rangeMin; index < rangeMax+stepSize/2; index+= stepSize)
{
    cout << index << endl;
}

Дополнительные сведения см. в разделе Что должен знать каждый программист об арифметике с плавающей запятой.

person Kirill V. Lyadvinsky    schedule 17.08.2009

Как упоминалось другими, это хорошо известная проблема из-за неточного представления некоторых десятичных чисел в памяти. Я настоятельно рекомендую прочитать Что каждый программист должен знать об арифметике с плавающей запятой и представления вещественных чисел IEEE с плавающей запятой.

person Antonio Cangiano    schedule 17.08.2009

Большинство точных десятичных дробей не имеют точного конечного представления в арифметике с плавающей запятой.

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

person caf    schedule 17.08.2009

Как уже говорили другие, не каждое действительное число точно можно представить как значение с плавающей запятой, поэтому вы можете ожидать небольшую «случайную» ошибку округления в вычислениях с плавающей запятой. Это похоже на то, что происходит с обычными десятичными цифрами: 1/3 нельзя точно представить с помощью трех десятичных цифр (0,33), поэтому (1/3)*3 станет 0,99, а не точно 1.

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

Например, ваш цикл

stepSize = 0.05;
for (double index = rangeMin; index <= rangeMax; index+= stepSize)
{
    cout << index << endl;
}

можно заменить чем-то вроде

stepSize = 0.05;
for (int index = 0; index < 21; ++index)
{
    double value = rangeMin + index * stepSize;
    cout << value << endl;
}
person Thomas Padron-McCarthy    schedule 17.08.2009

Вероятно, последнее значение index будет похоже на 1.00000001.

person Nick Dandoulakis    schedule 17.08.2009

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

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

person Mark Bessey    schedule 17.08.2009

Смотрите этот вывод: (точность с плавающей запятой)

#include <iostream>
#include <iomanip>
using namespace std;
int main(){
    double rangeMin = 0.0;
    double rangeMax = 1.0;
    double stepSize = 0.1;
    double index;
    for (index = rangeMin;  index <= rangeMax; index+=stepSize)
        {
               cout << fixed << setprecision(16) <<  index << endl;
         }
  cout << endl;
  stepSize = 0.05;
  for (index = rangeMin; index<= rangeMax; index+= stepSize)
     {
         cout << index << endl;
             }

   cout << "\n" << setprecision(16) << index << " "  << rangeMax;
   if(index==rangeMax)
      cout << "\nEQ";
   else
     cout << "\nNot EQ";
     return 0;
}

0.0000000000000000
0.1000000000000000
0.2000000000000000
0.3000000000000000
0.4000000000000000
0.5000000000000000
0.6000000000000000
0.7000000000000000
0.7999999999999999
0.8999999999999999
0.9999999999999999

0.0000000000000000
0.0500000000000000
0.1000000000000000
0.1500000000000000
0.2000000000000000
0.2500000000000000
0.3000000000000000
0.3500000000000000
0.4000000000000000
0.4500000000000000
0.4999999999999999
0.5499999999999999
0.6000000000000000
0.6500000000000000
0.7000000000000001
0.7500000000000001
0.8000000000000002
0.8500000000000002
0.9000000000000002
0.9500000000000003

1.0000000000000002 1.0000000000000000
Not EQ
person kv-prajapati    schedule 17.08.2009

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

#include<iostream>
#include<cmath>
#include<iomanip>
using namespace std; 

int main()
{
for (double y = 1; y!=10; y += 1)
    cout << static_cast<double>(y/10) << endl; 



}
person Nasir Nasrallah    schedule 05.02.2017