Вычисления с плавающей точкой на ардуино (Mega 2560)

Тема в разделе "Arduino & Shields", создана пользователем geher, 5 фев 2015.

  1. geher

    geher Гуру

    Перенес на ардуино код из книги Оливер Монтенбрук, Томас Пфлегер "Астрономия на персональном компьютере", касающийся рассчета времени восхода и захода Луны и Солнца для отображения на дисплее метеостанции. Получается отклонение от реальных данных до двух минут.
    Возникает вопрос. Это может быть погрешность вычислений с плавающей точкой такая большая, или продолжать искать ошибку?

    Код, собранный на ПК дает правильные результаты.
    Вроде переносил все практически без изменений (разве только заменил #include <cmath> на #include <math.h> и выкинул весь вывод в cerr).
    Разрядность int вроде влиять не должна, поскольку когда дело доходит до целочисленных значений, они явно не выходят за границу.
     
    Последнее редактирование: 5 фев 2015
  2. ANV

    ANV Гуру

    Код?
     
  3. geher

    geher Гуру

    Не знаю, уместно ли публиковать здесь код из диска-приложения к книге. Я-то не против, но защитники авторских прав не дремлют.
    Вот таким образом я использую код из книги.
    Код (Text):
    void GetInput ( int year, int month, int day, double& MJD, double& lambda,
                    double& phi, double& zone)
    {
      lambda*=Rad;
      phi*=Rad;
      zone/=24.0;

      MJD = Mjd(year,month,day) - zone;
    }
    int LTimeToReal(double t)
    {
      double HRound;
      int H, M;
      double S;
      HRound=floor(60.0*t+0.5)/60.0+0.00001;
      DMS (HRound, H, M, S);
      return(H*60+M);
    }
    void sunsetrise(int &T_Rise, int &T_Set, bool &rise, bool &sett, int year, int month, int day, double longitude, double latitude, int tz)
    {
      bool    above;
      double  start_date;
      double lambda=longitude;
      double phi=latitude;
      double zone=tz;
      double LT_Rise, LT_Set;
      GetInput(year,month,day,start_date,lambda,phi,zone);
      FindEvents ( (enEvent)1, start_date, lambda, phi,
                    LT_Rise, LT_Set, rise, sett, above );  
      T_Rise=LTimeToReal(LT_Rise);
      T_Set=LTimeToReal(LT_Set);
    }
    void moonsetrise(int &T_Rise, int &T_Set, bool &rise, bool &sett, int year, int month, int day, double longitude, double latitude, int tz)
    {
      bool    above;
      double  start_date;
      double lambda=longitude;
      double phi=latitude;
      double zone=tz;
      double LT_Rise, LT_Set;
      GetInput(year,month,day,start_date,lambda,phi,zone);
      FindEvents ( (enEvent)0, start_date, lambda, phi,
                    LT_Rise, LT_Set, rise, sett, above );  
      T_Rise=LTimeToReal(LT_Rise);
      T_Set=LTimeToReal(LT_Set);
    }
    В программе вызываются две последние функции, которые возвращают в параметрах
    два целых : T_Rise и T_Set
    и два булевских: rise и sett значения.
    Целые значения - это время восхода и захода в минутах с начала суток.
    булевские показывают, был ли этот самый восход или заход.
    Остальное без изменений (за исключением косметической правки, о которой ранее упоминал).
     
  4. snake32

    snake32 Нерд

    http://arduino.cc/en/Reference/Double
    Здесь пишут что Double на всех Arduino (кроме Arduino Due) занимает памяти как обычный float - 4 bytes, вместо должных 8(64bit). Поэтому чисто физически Double не может сохранять большую точность. Проблема в том что точно измерить точность сложно, ибо зависит от значений переменных и применяемых операций.
    Float, при использовании в диапазоне [-1..1] может хранить значения с точностью 1e-5. А ваши 2 минуты это (2/60) * (pi/180) = 5.8e-4 Что в принципе не так уж и далеко учитывая что у вас рабочий диапазон значений выходит за рамки [-1..1].

    Думаю, правильность алгоритма можно проверить так: заменить на ПК все вычисления double на float. При одинаковых входных данных разница должна быть значительно меньше.
     
  5. geher

    geher Гуру

    Спасибо.
    Все похоже на то, что все-таки дело в точности вычислений на МК, а не в случайно внесенной ошибке.
    Интересно, а как-нибудь можно добиться приличной точности с плавающей точкой на ардуино? Например, какая-нибудь библиотека с программной реализацией этого дела, чтобы double был полноценным?
     
  6. snake32

    snake32 Нерд

    Таких либ к сожалению не знаю. Если кто знает поделитесь. Видел как успешно пытаются добиться высокой точности на флоатах в шейдере GLSL(те что на видеокартах) вычисляя фрактал( https://ru.wikipedia.org/wiki/Множество_Мандельброта)
    Даже с учетверённой точностью относительно флоата https://www.thasler.com/blog/blog/glsl-part5
     
  7. Unixon

    Unixon Оракул Модератор

    Ее можно сделать, но насколько это осмысленно - большой вопрос.
    Вот тут расписали ситуацию с gcc / avr-libc: http://www.avrfreaks.net/comment/779607#comment-779607
    Повозиться придется порядочно...

    Может быть, лучше будет сменить платформу, чем мучать 8-битную AVR-ку?
     
  8. geher

    geher Гуру

    Спасибо. Решения, получается, есть. Только придется очень сильно повозиться с исходниками, уж очень много там разнообразных вычислений с double. Возникает даже вопрос, а стоит ли улучшение точности вычисления времени восходов-заходов на домашней метеостанции таких расходов времени.

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