Почему этот код возвращает два разных значения, делающих то же самое?

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
    double a;
    double b;
    double q0 = 0.5 * M_PI + 0.5 * -2.1500000405000002;
    double q1 = 0.5 * M_PI + 0.5 * 0.0000000000000000;
    double w0 = 0.5 * M_PI + 0.5 * -43000.0008100000050000;
    double w1 = 0.5 * M_PI + 0.5 * -0.0000000000000000;
    double m = 1;
    double g = 43000000.81;
    double l1 = 0.1;
    double l2 = 0.1;
    double h = 0.0001;

    a = ((-g / l1) * sin(q0) + (sin(q1 - q0) * (cos(q1 - q0) * (w0 * w0 + (g / l1) * cos(q0)) + l2 * (w1 * w1 / l1))) / (m + pow(sin(q1 - q0), 2)));
    a = h * a;

    b = h * ((-g / l1) * sin(q0) + (sin(q1 - q0) * (cos(q1 - q0) * (w0 * w0 + (g / l1) * cos(q0)) + l2 * (w1 * w1 / l1))) / (m + pow(sin(q1 - q0), 2)));

    printf("%.20lf ", a);
    printf("%.20lf", b);
    return 0;
}

Я делаю те же вычисления с a и b, только с той разницей, что получаю значение a за два шага, а b за один.

Мой код возвращает: -629.47620126173774000000 -629.47620126173763000000

В чем причина разницы между двумя последними десятичными знаками?

2 ответа

Решение

Стандарт C (99 и 11) гласит:

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

Так в выражении, таком как h*(X+Y) как у вас в задании b реализация позволяет использовать большую точность для промежуточного результата X+Y чем может быть сохранено в double, хотя тип подвыражения все еще считается double, Но в a=X+Y; a=h*a; первое назначение заставляет значение быть тем, которое фактически может быть сохранено в double, вызывая немного другой результат.

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

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

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

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

Страница cppreference о макросе FLT_EVAL_METHOD обсуждает оба эти вопроса чуть более подробно. Может быть интересно узнать вашу ценность FLT_EVAL_METHOD и играть с #pragma STDC FP_CONTRACT OFF,

Ответ заключается в том, что в вычислениях чисел с плавающей точкой уравнение a = bc de не обязательно должно быть равно x = b c y = de и a.= X y.

Это потому, что арифметика с плавающей запятой имеет ограниченную точность.

Другие вопросы по тегам