Как проверить точность с плавающей запятой в Double - PullRequest
0 голосов
/ 27 октября 2018

Возьмем, к примеру, эту простую функцию.

   int checkIfTriangleIsValid(double a, double b, double c) {
      //fix the precision problem
      int c1, c2, c3;
      c1 = a+b>c ? 0 : 1;
      c2 = b+c>a ? 0 : 1;
      c3 = c+a>b ? 0 : 1;
      if(c1 == 0 && c2 == 0 && c3 == 0)
        return 0;
      else {
        printf("%d, %d, %d\n",c1, c2, c3);
        return 1;
      }
   } 

Я ставлю a = 1.923, b = 59.240, c = 61.163

Теперь по какой-то причине, когда я проверяю условие вc1 он должен дать мне 1, но вместо этого он дает мне 0. Я попытался сделать printf с %.30f и обнаружил, что значения позже меняются.

Как я могу исправить эту проблему?

РЕДАКТИРОВАТЬ: Я проверил другие вопросы, которые похожи на мои, но они даже не имеют двойной.

Ответы [ 2 ]

0 голосов
/ 27 октября 2018

, когда я проверяю условие в c1, оно должно давать мне 1, но вместо этого оно дает мне 0
Как я могу решить эту проблему?

Измените свои ожидания.

Типичный double может представлять точно около 2 64 различных значений.1.923, 59.240, 61.163 обычно не входят в этот набор, поскольку double обычно кодируется двоичным способом.например, binary64 .

Когда a,b,c назначены 1.923, 59.240, 61.163, они получают значения, более похожие на приведенные ниже, которые представляют собой шкаф double.

a      1.923000000000000042632564145606...
b     59.240000000000001989519660128281...
c     61.162999999999996703081706073135...

В моем случае a и b получили немного более высокое значение, чем форма десятичного кода, в то время как c получили немного меньшее значение.

При добавлении a+b суммабыл округлен в большую сторону, дальше от c.

printf("a+b %35.30f\n", a+b);
a+b   61.163000000000003808509063674137

a + b > c, а также других сравнений и OP
checkIfTriangleIsValid(1.923, 59.240, 61.163) должен вернуться Действительный (0) , поскольку это действительно больше похоже на checkIfTriangleIsValid(1.9230000000000000426..., 59.24000000000000198..., 61.16299999999999670...)


Добавление a+b еще более усложняется тем, что добавление может происходить с использованием double или long double математикаИсследование FLT_EVAL_METHOD для деталей.Режим округления также может повлиять на итоговую сумму.

#include <float.h>
printf("FLT_EVAL_METHOD %d\n", FLT_EVAL_METHOD);

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

a > (c-b) может сохранять значительно большую точность, чем (a+b) > c.

// Assume a,b,c >= 0
int checkIfTriangleIsValid_2(double a, double b, double c) {
  // Sort so `c` is largest, then b, a.
  if (c < b) {
    double t = b; b = c; c = t;
  }
  if (c < a) {
    double t = a; a = c; c = t;
  }
  if (a > b) {
    double t = b; b = a; a = t;
  }
  // So far, no loss of precision is expected due to compares/swaps.
  // Only now need to check a + b >= c for valid triangle

  // To preserve precision, subtract from `c` the value closest to it (`b`).
  return a > (c-b);
}

Я расскажу подробнее позже, если позволит время.Такой подход существенно помогает получить точный ответ, но при этом необходимо оценить больше крайних случаев.Сообщается действительный треугольник checkIfTriangleIsValid_2(1.923, 59.240, 61.163)).

FLT_EVAL_METHOD, режим округления и кодирование double могут привести к различным ответам на других платформах.


Примечания:
Похоже, checkIfTriangleIsValid() возвращает 0 означает действительный треугольник .
Также появляется, когда треугольник имеет область 0, ожидаемый результат равен 1 или недействительно .

0 голосов
/ 27 октября 2018

Вероятно, ваша реализация C использует базовый 64-битный двоичный формат IEEE-754 для double.Когда 1.923, 59.240 и 61.163 должным образом преобразованы в ближайшие значения, представленные в double, результаты будут точно такими:

  • 1.9230000000000000426325641456060111522674560546875,
  • 59.24000000000000828519595825525175525525825875875875875575875895816000000000000000000000000000000000000000000000000000000000000000000000000000000000000остивалисокивали по XT.C.C.и
  • 61.1629999999999967030817060731351375579833984375.

Как видите, первые два из этих сумм больше, чем третьи.Это означает, что к тому времени, когда вы присваиваете эти значения double объектам, они уже были изменены таким образом, что изменяет их отношения. Никакие последующие вычисления не могут исправить это, поскольку исходная информация исчезла.

Поскольку решение после преобразования в double работать не может, вам нужно решение, которое работаетдо или вместо преобразования в double.Если вы хотите точно или более точно вычислить значения 1.923, 59.240 и 61.163, вам может потребоваться написать собственный десятичный арифметический код или найти другой код, который поддерживает десятичную арифметику.Если вы хотите работать только с числами с тремя десятичными знаками, то возможное решение состоит в том, чтобы написать некоторый код, который считывает ввод, такой как «59.240», и возвращает его в целочисленном объекте, масштабированном на 1000, так что возвращается 59240.Полученные значения можно легко проверить на неравенство треугольника.

...