matlab и c отличаются функцией cos - PullRequest
8 голосов
/ 01 октября 2011

У меня есть программа, реализованная в matlab, и та же программа на c, и результаты отличаются.

Я немного озадачен тем, что функция cos не возвращает точно такой же результат.

Я использую один и тот же компьютер, Intel Core 2 Duo и 8-байтовый двойной тип данных в обоих случаях.

Почему результат отличается?

Вот тест:

c:
double a = 2.89308776595231886830;
double b = cos(a);
printf("a = %.50f\n", a);
printf("b = %.50f\n", b);
printf("sizeof(a): %ld\n", sizeof(a));
printf("sizeof(b): %ld\n", sizeof(b));

a = 2.89308776595231886830106304842047393321990966796875
b = -0.96928123535654842068964853751822374761104583740234
sizeof(a): 8
sizeof(b): 8



matlab:
a = 2.89308776595231886830
b = cos(a);
fprintf('a = %.50f\n', a);
fprintf('b = %.50f\n', b);
whos('a')
whos('b')

a = 2.89308776595231886830106304842047393321990966796875
b = -0.96928123535654830966734607500256970524787902832031
  Name      Size            Bytes  Class     Attributes
  a         1x1                 8  double              

  Name      Size            Bytes  Class     Attributes
  b         1x1                 8  double  


So, b differ a bit (very slightly, but enough to make my debuging task difficult)

b = -0.96928123535654842068964853751822374761104583740234  c
b = -0.96928123535654830966734607500256970524787902832031  matlab

Я использую тот же компьютер, Intel Core 2 Duo и 8-байтовый тип данных с двойным байтом.

Почему результат отличается?

Matlab не использует функцию cosвстроенное в Intel аппаратное обеспечение?

Существует ли простой способ использовать одну и ту же функцию cos в matlab и c (с точными результатами), даже если немного медленнее, чтобы я мог безопасно сравнивать результаты моегоПрограмма Matlab и C?


Обновление:

большое спасибо за ваши ответы!

Итак, как вы указали,функция cos для matlab и c отличается.Это восхитительно!Я думал, что они используют функцию cos, встроенную в микропроцессор Intel.

Версия cos matlab равна (по крайней мере для этого теста) версии matlab.Вы также можете попробовать из matlab: b = java.lang.Math.cos (a)

Затем я сделал небольшую функцию MEX для использования версии cos c изнутри matlab, и она отлично работает;Это позволяет мне отлаживать мою программу (ту же самую, что реализована в matlab и c) и посмотреть, в чем они отличаются, что и было целью этого поста.

Единственное, что вызывает MEX c cosверсия из matlab слишком медленная.

Я сейчас пытаюсь вызвать функцию Java cos из c (как и в matlab), посмотрите, будет ли она быстрее.

Ответы [ 4 ]

6 голосов
/ 01 октября 2011

Числа с плавающей запятой хранятся в двоичном, а не в десятичном виде.Точность с плавающей точкой double имеет 52 бита, что соответствует примерно 15 значащим десятичным разрядам.Другими словами, первых 15 ненулевых десятичных цифр double, напечатанных в десятичном виде, достаточно, чтобы однозначно определить, какой double был напечатан.* имеет точное представление в десятичном виде, которое занимает гораздо больше десятичных разрядов, чем 15 для представления (в вашем случае, я думаю, 52 или 53 знака).Однако стандарты для printf и аналогичных функций не требуют, чтобы цифры после 15-го числа были правильными;они могут быть полной ерундой.Я подозреваю, что одна из двух сред печатает точное значение, а другая печатает плохое приближение, и что в действительности оба соответствуют одному и тому же двоичному значению double.

5 голосов
/ 01 октября 2011

Использование сценария на http://www.mathworks.com/matlabcentral/fileexchange/1777-from-double-to-string

разница между двумя числами только в последнем бите:

octave:1> bc = -0.96928123535654842068964853751822374761104583740234;
octave:2> bm = -0.96928123535654830966734607500256970524787902832031;
octave:3> num2bin(bc)
ans = -.11111000001000101101000010100110011110111001110001011*2^+0
octave:4> num2bin(bm)
ans = -.11111000001000101101000010100110011110111001110001010*2^+0

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

>> be = vpa('cos(2.89308776595231886830)',50)                 
be =
-.96928123535654836529707365425580405084360377470583
>> bc = -0.96928123535654842068964853751822374761104583740234;
>> bm = -0.96928123535654830966734607500256970524787902832031;
>> abs(bc-be)                                                 
ans =
.5539257488326242e-16
>> abs(bm-be)                                                 
ans =
.5562972757925323e-16

Итак, результат библиотеки C более точный.

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

4 голосов
/ 01 октября 2011

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

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

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

2 голосов
/ 02 октября 2011

Как уже объясняли другие, когда вы вводите это число непосредственно в исходном коде, будут использоваться не все цифры дроби, поскольку вы получите только 15/16 десятичных знаков для точности. Фактически они преобразуются в двоичное значение с ближайшим двойным значением (все, что находится за пределами фиксированного предела цифр, отбрасывается).

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

Чтобы проиллюстрировать это, я протестировал следующий MEX-файл, один раз скомпилированный с компилятором LCC по умолчанию, а затем с использованием VS2010 (я использую 32-битную версию WinXP).

В одной функции мы напрямую вызываем функции C (mexPrintf - это просто макрос #define как printf). В другом случае мы вызываем mexEvalString для проверки содержимого механизма MATLAB (эквивалентно использованию командной строки в MATLAB).

prec.c

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

void c_test()
{
    double a = 2.89308776595231886830L;
    double b = cos(a);

    mexPrintf("[C] a =  %.25Lf (%16Lx)\n", a, a);
    mexPrintf("[C] b = %.25Lf (%16Lx)\n", b, b);
}

void matlab_test()
{
    mexEvalString("a = 2.89308776595231886830;");
    mexEvalString("b = cos(a);");

    mexEvalString("fprintf('[M] a =  %.25f (%bx)\\n', a, a)");
    mexEvalString("fprintf('[M] b = %.25f (%bx)\\n', b, b)");
}

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    matlab_test();
    c_test();
}

соединено с LCC

>> prec
[M] a =  2.8930877659523189000000000 (4007250b32d9c886)
[M] b = -0.9692812353565483100000000 (bfef045a14cf738a)
[C] a =  2.8930877659523189000000000 (        32d9c886)
[C] b = -0.9692812353565484200000000 (        14cf738b)    <---

скомпилировано с VS2010

>> prec
[M] a =  2.8930877659523189000000000 (4007250b32d9c886)
[M] b = -0.9692812353565483100000000 (bfef045a14cf738a)
[C] a =  2.8930877659523189000000000 (        32d9c886)
[C] b = -0.9692812353565483100000000 (        14cf738a)    <---

Я компилирую вышеупомянутое, используя: mex -v -largeArrayDims prec.c, и переключаюсь между внутренними компиляторами, используя: mex -setup

Обратите внимание, что я также пытался напечатать шестнадцатеричное представление чисел. Мне удалось показать только нижнюю половину двоичных двойных чисел в C (возможно, вы можете получить другую половину, используя какие-то битовые манипуляции, но я не уверен, как!)

Наконец, если вам нужна большая точность в ваших вычислениях, рассмотрите возможность использования библиотеки для арифметики с переменной точностью. В MATLAB, если у вас есть доступ к Symbolic Math Toolbox , попробуйте:

>> a = sym('2.89308776595231886830');
>> b = cos(a);
>> vpa(b,25)
ans =
-0.9692812353565483652970737

Итак, вы можете видеть, что фактическое значение находится где-то между двумя различными приближениями, которые я получил выше, и на самом деле все они равны до 15-го знака после запятой:

-0.96928123535654831..    # 0xbfef045a14cf738a
-0.96928123535654836..    # <--- actual value (cannot be represented in 64-bit)
-0.96928123535654842..    # 0xbfef045a14cf738b
                 ^
    15th digit --/

UPDATE:

Если вы хотите правильно отобразить шестнадцатеричное представление чисел с плавающей запятой в C, используйте вместо этого эту вспомогательную функцию (аналогично NUM2HEX функции в MATLAB):

/* you need to adjust for double/float datatypes, big/little endianness */
void num2hex(double x)
{
    unsigned char *p = (unsigned char *) &x;
    int i;
    for(i=sizeof(double)-1; i>=0; i--) {
        printf("%02x", p[i]);
    }
}
...