Показано с 1 по 10 из 639

Тема: Создать функцию на ST

Древовидный режим

Предыдущее сообщение Предыдущее сообщение   Следующее сообщение Следующее сообщение
  1. #11

    По умолчанию

    Я понял.
    В самом начале функции я делаю приведение аргумента к диапазону 0...2*Pi.
    Что в принципе, правильно.

    Но в самом начале вычислений при x=6 (чуть меньше 2*Pi), степенная составляющая растёт быстрее факториальной и получается потеря разрядности при сложении с более мелкими слагаемыми.
    Тут нужно или приводить к диапазону [-Pi...+Pi]. Хотя не проверял, но степенная часть будет расти медленнее и возможно, проблема уйдёт на более дальние незаметные разряды.
    Или сделать через формулу двойного аргумента.

    Сейчас поколдую, повспоминаю - уже решал на форуме именно задачу потери разрядности.
    https://www.cyberforum.ru/c-beginner...l#post14864162

    И в исходном сообщении исправлю, а в этом добавлю, но позже удалю - чтобы не засорять тему.
    ----------------------------------------

    Видимо, задача сложнее, чем казалась сначала.
    Скрытый текст:
    Код:
    FUNCTION sin_enh: REAL;
    
        VAR_INPUT
            x: REAL;
        END_VAR
    
        VAR
            Pi: REAL := 3.1415926535897932384626433832795;
            Pi_x2: REAL := 6.2831853071795864769252867665590;
            // Pi_2:real := 1.5707963267948966192313216916398;
            // Pi_4:real := 0.78539816339744830961566084581988;
            // переменные для вычислений по формуле двойного аргумента
            n: UDINT;
            sin_2x, cos_2x: REAL;
            sin_x, cos_x: REAL;
            // переменные для вычислений ряда Тейлора
            f: REAL;
            a_sin, a_cos: REAL;
        END_VAR
        // показательная функция [x^(2n+1)] в первых членах суммы ряда Тейлора
        // растёт быстрее факториала [(2n+1)!], что приводит к потере точности
        // буквально с первых слагаемых
        // Решения:
        // 1. приведение аргумента к диапазону 0...2*Pi - исходной области определения sin
        // 2. приведение аргумента к эквивалентному диапазону 0...+Pi,
        // при этом меняется знак аргумента, который был больше Pi
        // sin(t) = sin(-x+Pi) = sin(-x)cos(Pi) + sin(Pi)cos(-x) = -sin(-x) = sin(x)
        // x = Pi - t
        // 3. приведение аргумента к диапазону -Pi/2...+Pi/2
        // sin(t) = sin(x+Pi/2)= sin(x)cos(Pi/2) + sin(Pi/2)cos(x) = -cos(x)
    
        (*
        if x < 0 then
        n := real_to_udint(abs(x) / Pi_x2) + 1;
        // n:=0;
        x := x + udint_to_real(n) * Pi_x2;
        end_if;
        if x > Pi_x2 then
        n := real_to_udint(abs(x) / Pi_x2);
        // n:=0;
        x := x - udint_to_real(n) * Pi_x2;
        end_if
        *)
    
        // для проверки сразу привожу к проблемному диапазону
        x := x + Pi + Pi;
    
        // приводим аргумент к приемлемым значениям для вычисления
        n := 0;
        WHILE (ABS(x) > 1.5) DO
            x := x / 2.0;
            n := n + 1;
        END_WHILE
        // вычисляем первое приближение через ряд Тейлора
        f := 1;
        a_sin := x;
        sin_x := x;
        a_cos := 1;
        cos_x := 1;
        x := x * x;
        REPEAT
            a_sin := - a_sin * x / ((f + 1) * (f + 2));
            a_cos := - a_cos * x / (f * (f + 1));
            sin_x := sin_x + a_sin;
            cos_x := cos_x + a_cos;
            f := f + 2;
            IF f > 30 THEN
                EXIT;
            END_IF;
        UNTIL (ABS(a_sin) < 0.0000001) AND (f > 24)
        END_REPEAT
        // вычисляем для исходного аргумента
        WHILE (n > 0) DO
            sin_2x := 2 * sin_x * cos_x;
            cos_2x := 1 - 2 * sin_x * sin_x;
    
            sin_x := sin_2x;
            cos_x := cos_2x;
    
            n := n - 1;
        END_WHILE
    
        sin_enh := sin_x;
    END_FUNCTION

    Попробовал уйти от проблемы потери точности через формулу двойного аргумента - сначала несколькими делениями на 2 получить маленькое значение x, а потом для маленького x получить sin и cos и далее выполнить пересчёт к исходному аргументу.
    Но тип REAL даёт всего 7-8 точных десятичных знаков и каждое умножение снижает точность на 1 разряд, т.е. все вычисления проходят на границе точности.
    Т.е. и уменьшение аргумента к повышению точности до предельных 7 разрядов не привело.

    Наверное, нужно попробовать сделать приведением аргумента к диапазону -Pi/4...+Pi/4 и там уже рядом условий вычислять или sin или cos от приведённого аргумента.
    Последний раз редактировалось FPavel; 04.02.2026 в 08:12.

Похожие темы

  1. Ответов: 14
    Последнее сообщение: 01.07.2023, 21:30
  2. Ответов: 6
    Последнее сообщение: 22.12.2021, 10:50
  3. Ответов: 3
    Последнее сообщение: 13.09.2021, 13:31
  4. ПЛК160. Чем заменить функцию записи 0x05?
    от FallenDAY в разделе ПЛК1хх
    Ответов: 4
    Последнее сообщение: 26.08.2017, 13:19
  5. Как написать собственную функцию wait()
    от PavelKazakov в разделе ПЛК1хх
    Ответов: 3
    Последнее сообщение: 23.07.2009, 11:37

Метки этой темы

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •