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

StackOverflow https://stackoverflow.com/questions/1286394

  •  18-09-2019
  •  | 
  •  

Вопрос

У меня есть простая программа на 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$
Это было полезно?

Решение

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

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

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

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

Другие советы

Обычно, когда вы сравниваете двойные значения, простого сравнения недостаточно, и вам следует сравнивать их «с точностью до точности».то есть:

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

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

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

Не следует использовать == или <= для двойных значений из-за их внутреннего представления.На последнем шаге вы получите 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;
}

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

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

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

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

Как уже говорили другие, не каждое действительное число точно представляется как значение с плавающей запятой, поэтому вы можете ожидать небольшую «случайную» ошибку округления в вычислениях с плавающей запятой.Это похоже на то, что происходит с обычными десятичными цифрами: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;
}

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

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

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

#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

Как и в предыдущих ответах, использовать нецелые числа в циклах 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; 



}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top