Ошибки с плавающей точкой в ​​C ++ - PullRequest
1 голос
/ 05 февраля 2012

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

Для исправления ошибок я использую следующий код:

do 
{
    float fValue = float(x) / 1024.f;
    double oldFValue = fValue;
    double dValue = double(x) / 1024.0;
    if(oldFValue != dValue)
    {
        x += 1;
    }
    else
    {
        break;
    }
}while(1);

С этим кодом для

x = 11 

У меня в отладчике (Visual Studio 2010):

fValue = 0.010742188
oldFValue = 0.010742187500000000

Не могли бы вы объяснить, почему значение douable отличается от значения с плавающей запятой? Это проблема отладчика или проблема преобразования с плавающей запятой? Я спрашиваю это, потому что:

if(oldFValue != dValue)

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

Ответы [ 5 ]

10 голосов
/ 05 февраля 2012
2 голосов
/ 05 февраля 2012

11/1024 точно представлен как в float, так и в double. Ну и конечно oldFValue == dValue.

2 голосов
/ 05 февраля 2012

Сколько вы знаете о с плавающей запятой одинарной точности ?

Он хранится как <sign><exponent><mantis>.Вы можете написать окончательное число как:

(sign ? 1 : -1) * 0.1<mantis> * 2^(expontent - 127)

Как видите, число ВСЕГДА хранится как число >1 и в виде двоичной дроби.К сожалению, некоторые числа, такие как 0.1 dec, являются периодическими в двоичном формате, поэтому вы не получите точный результат с помощью float.

Вы можете попробовать использовать это: if(oldFValue != (float)dValue), и если это не сработает, вы также можете попробовать:

if(oldFValue*32 != (float)dValue*32)

Это приведет к:

mantis >> 5
expontent += 5

, который может устранить вашу ошибку (попробуйте 1 (странно, но может работать в некоторых случаях), 2, 4, 8, 16 ..., 2 ^ n).

РЕДАКТИРОВАТЬ : окончательно прочитать Johnsywebs link

1 голос
/ 13 апреля 2018

Большинство операций с плавающей запятой выполняются с потерей данных в мантиссе, даже когда компоненты хорошо вписываются в нее (числа, такие как 0,5 или 0,25). Например

a + b + c

не совпадает с

a + c + b

Также важен заказ многофункциональных компонентов.

Чтобы решить проблему, необходимо знать, как числа fp представлены машинами.

Возможно, это поможет: http://stepan.dyatkovskiy.com/2018/04/machine-fp-partial-invariance-issue.html

Ниже приведен пример C + a + b + c. Удачи!

example.c

#include <stdio.h>

// Helpers declaration, for implementation scroll down
float getAllOnes(unsigned bits);
unsigned getMantissaBits();

int main() {

  // Determine mantissa size in bits
  unsigned mantissaBits = getMantissaBits();

  // Considering mantissa has only 3 bits, we would then get:
  // a = 0b10   m=1,  e=1
  // b = 0b110  m=11, e=1
  // c = 0b1000 m=1,  e=3
  // a + b = 0b1000, m=100, e=1
  // a + c = 0b1010, truncated to 0b1000, m=100, e=1
  // a + b + c result: 0b1000 + 0b1000 = 0b10000, m=100, e=2
  // a + c + b result: 0b1000 + 0b110 = 0b1110, m=111, e=1

  float a = 2,
        b = getAllOnes(mantissaBits) - 1,
        c = b + 1;

  float ab = a + b;
  float ac = a + c;

  float abc = a + b + c;
  float acb = a + c + b;

  printf("\n"
         "FP partial invariance issue demo:\n"
         "\n"
         "Mantissa size = %i bits\n"
         "\n"
         "a = %.1f\n"
         "b = %.1f\n"
         "c = %.1f\n"
         "(a+b) result: %.1f\n"
         "(a+c) result: %.1f\n"
         "(a + b + c) result: %.1f\n"
         "(a + c + b) result: %.1f\n"
         "---------------------------------\n"
         "diff(a + b + c, a + c + b) = %.1f\n\n",
         mantissaBits,
         a, b, c,
         ab, ac,
         abc, acb,
         abc - acb);

  return 1;
}

// Helpers

float getAllOnes(unsigned bits) {
    return (unsigned)((1 << bits) - 1);
}

unsigned getMantissaBits() {

    unsigned sz = 1;
    unsigned unbeleivableHugeSize = 1024;
    float allOnes = 1;

    for (;sz != unbeleivableHugeSize &&
          allOnes + 1 != allOnes;
          allOnes = getAllOnes(++sz)
          ) {}

    return sz-1;
}
1 голос
/ 05 февраля 2012

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

Таким образом - числа с плавающей запятой не являются действительными / рациональными числами - и ведут себя по-разному. Вы должны ожидать, что это будет не совсем так, как действовало бы действительное число.

По этой причине вам никогда не следует проверять равенство чисел с плавающей запятой, используя operator==. Вы должны вычислить delta=abs(num1-num2) и проверить, меньше ли оно, чем какое-либо значение, с которым вы можете допустить его ошибку.

Как сказал @Johnsyweb, чтение и понимание прилагаемой статьи важно для правильной работы с плавающей точкой.

...