Страница 62 из 64 ПерваяПервая ... 12526061626364 ПоследняяПоследняя
Показано с 611 по 620 из 634

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

  1. #611

    По умолчанию

    Я понял.
    В самом начале функции я делаю приведение аргумента к диапазону 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.

  2. #612

    По умолчанию

    Добрый день!

    Алиса и с синусом готова помочь:

    FUNCTION SinTaylorReal : REAL;
    VAR_INPUT
    x : REAL; (* Угол в радианах *)
    tolerance: REAL; (* Точность, например, 1E-6 *)
    END_VAR
    VAR
    result : REAL := 0.0; (* Текущая сумма ряда *)
    term : REAL := 0.0; (* Текущий член ряда *)
    x_power : REAL := 0.0; (* x^(2n+1) *)
    factorial: REAL := 1.0; (* (2n+1)! *)
    n : UDINT := 0; (* Номер члена ряда (целочисленный счётчик) *)
    sign : REAL := 1.0; (* Знак члена: +1 или -1 *)
    two_n_plus_1: UDINT; (* 2n + 1 *)
    i, j : UDINT;
    END_VAR

    (* Нормализация угла: приводим x к [-2π, 2π] *)
    WHILE ABS(x) > 2.0 * 3.1415926535 DO
    IF x > 0.0 THEN
    x := x - 2.0 * 3.1415926535;
    ELSE
    x := x + 2.0 * 3.1415926535;
    END_IF
    END_WHILE

    (* Вычисление ряда Тейлора до достижения точности *)
    WHILE TRUE DO
    (* Вычисляем 2n + 1 *)
    two_n_plus_1 := 2 * n + 1;

    (* Вычисляем x^(2n+1) *)
    x_power := 1.0;
    FOR i := 1 TO two_n_plus_1 BY 1 DO
    x_power := x_power * x;
    END_FOR

    (* Вычисляем (2n+1)! *)
    factorial := 1.0;
    FOR j := 1 TO two_n_plus_1 BY 1 DO
    factorial := factorial * udint_to_real(j); (* Приводим UDINT к REAL *)
    END_FOR

    (* Текущий член ряда: (-1)^n * x^(2n+1) / (2n+1)! *)
    term := sign * x_power / factorial;

    (* Добавляем к результату *)
    result := result + term;

    (* Проверяем точность: если модуль члена < tolerance, завершаем *)
    IF ABS(term) < tolerance THEN
    EXIT;
    END_IF

    (* Переходим к следующему члену: n = n + 1, меняем знак *)
    n := n + 1;
    sign := -sign;
    END_WHILE

    SinTaylorReal := result;
    END_FUNCTION

  3. #613

    По умолчанию

    Прямое вычисление факториала - быстро приведёт к переполнению разрядной сетки переменной.
    Поэтому и используют реккурентное соотношение между соседними слагаемыми.

    Но всё равно остаётся проблема точности при вычислениях с аргументами, превышающими 1,0-1,5, т.к. для первых слагаемых числитель (x^[2n]) растёт быстрее знаменателя ([2n]!), что приводит к сложению чисел очень с очень большой разницей, что усугубляется тем, что тип real способен хранить только 7-8 точных цифр (всего и целой и дробной частей).

    Попытка снизить аргумент пересчётом по формуле удвоенного аргумента опять приводит к потере точности из-за увеличения количества операций умножения.

    Пока я вижу, но не проверил кодом, решение в приведении аргумента к диапазону -Pi/2...+Pi/2 и вычислении sin или cos - вместо исходного sin.

    Так что решение от Алисы можно забыть - проверьте 7 первых чисел результата вычислений с ответом Калькулятора для x=-0.227. Увидите, что результат даёт только 5 верных знаков.

    Если есть желание - попробуйте сделать приведение к диапазону Pi/2.

  4. #614

    По умолчанию

    Странно, для x=-0.227 калькулятор выдаёт -0,225055503 Симулятор Овен Лоджика -0,2250555. Толеранс 1E-06 не забыли установить?

  5. #615

    По умолчанию

    Прошу прощения...
    x=-0.227 это уже приведённое значение из узкого диапазона.

    проверьте для
    x=-0.227+2*Pi=6.056185307179586476925286766559

    Тогда проявится

  6. #616

    По умолчанию

    Точность падает при числах близких к 1, проверять надо 0,99 и -0,99

    И самое главное, одна такая функция (от Алисы), при высокой точности (1000 итераций) делает цикл ПР близким к 72 мс, что ни в какие ворота не лезет https://owen.ru/forum/showthread.php...l=1#post479364
    Последний раз редактировалось kondor3000; 06.02.2026 в 10:26.

  7. #617

    По умолчанию

    Формулу для горизонтальной бочки записывал в ПР 200 , значение высоты водяного столба вводил с экрана, - датчик пока не купили - тормозов не заметил, точность соответствует онлайн калькулятору. Это касательно обратных триг. функций. А Вам синус зачем, если не секрет?

  8. #618
    Пользователь Аватар для capzap
    Регистрация
    25.02.2011
    Адрес
    Киров
    Сообщений
    10,622

    По умолчанию

    Цитата Сообщение от Рогов Алексей Посмотреть сообщение
    Добрый день!

    Алиса и с синусом готова помочь:
    если просто написать дай функцию без анализа что она пишет то наверное будут претензии вот прокрученная второй раз с уточнением что она эксперт в промышленной атоматизации
    Скрытый текст:

    Ваша функция реализует ряд Тейлора для синуса, но содержит критические недостатки для промышленного применения:
    🔴 Проблемы:

    Неэффективное вычисление степени и факториала
    На каждой итерации заново вычисляются x^(2n+1) и (2n+1)! через вложенные циклы — это катастрофически медленно на ПЛК (каждая итерация требует ~2n операций умножения).
    Переполнение факториала
    Для 32-битного REAL (IEEE 754) факториал переполняется уже при (2n+1)! > 3.4&#215;10&#179;⁸ → n ≥ 11. При этом для точности 1E-6 достаточно 4–5 итераций.
    Неточная нормализация угла
    Цикл WHILE ABS(x) > 2π работает медленно для больших углов (например, при x = 1000 радиан потребуется ~160 итераций). Константа π задана с недостаточной точностью (9 знаков вместо 7–8 значащих для REAL).
    Избыточная нормализация до [-2π, 2π]
    Для синуса достаточно приведения к [-π, π] — это ускоряет сходимость ряда в 2 раза.

    Оптимизированный вариант (рекуррентная формула)
    Код:
    FUNCTION SinTaylorOptimized : REAL
    VAR_INPUT
        x : REAL;                 // Угол в радианах
        tolerance : REAL := 1E-6; // Точность (по умолчанию)
    END_VAR
    VAR
        pi : REAL := 3.1415927;   // Достаточно для 32-битного REAL
        two_pi : REAL := 2.0 * pi;
        term : REAL;              // Текущий член ряда
        x_sq : REAL;              // x&#178; (вычисляется один раз)
        n : UDINT := 1;           // Счётчик итераций (начинаем с 1 для второго члена)
    END_VAR
    
    // === ШАГ 1: Нормализация к [-π, π] за О(1) ===
    x := x - two_pi * TRUNC(x / two_pi); // Быстрое приведение через целую часть
    IF x > pi THEN
        x := x - two_pi;
    ELSIF x < -pi THEN
        x := x + two_pi;
    END_IF;
    
    // === ШАГ 2: Рекуррентное вычисление ряда ===
    SinTaylorOptimized := x;      // Первый член: x
    term := x;
    x_sq := x * x;                // x&#178; вычисляем один раз
    
    // Итерации: term_{k} = -term_{k-1} * x&#178; / ((2k)*(2k+1))
    WHILE ABS(term) >= tolerance DO
        term := -term * x_sq / REAL(2 * n) / REAL(2 * n + 1);
        SinTaylorOptimized := SinTaylorOptimized + term;
        n := n + 1;
    END_WHILE;
    END_FUNCTION
    Ключевые улучшения:
    Рекуррентная формула
    termₙ = -termₙ₋₁ · x&#178; / ((2n)·(2n+1)) — исключает степень и факториал.
    Нормализация за константное время
    Через TRUNC(x / 2π) вместо цикла.
    Минимизация операций
    3 арифметические операции на итерацию против 2n в исходном коде.
    Безопасность для ПЛК
    Нет риска переполнения, стабильная работа с 32-битным
    Важно для промышленного применения: Для критичных к скорости задач (цикл ПЛК < 10 мс) рекомендуется использовать аппаратную функцию SIN() из библиотеки ПЛК — она реализована через таблицы и полиномы Чебышёва и работает в 10–100 раз быстрее. Ряд Тейлора оправдан только при отсутствии встроенной функции или для учебных целей.
    Bad programmers worry about the code. Good programmers worry about data structures and their relationships

    среди успешных людей я не встречала нытиков
    Барбара Коркоран

  9. #619

    По умолчанию

    Лично мне sin требуется при моделировании периодической помехи для проверки очередного регулятора.

    Например,
    https://owen.ru/forum/showthread.php...l=1#post474213
    https://owen.ru/forum/showthread.php...l=1#post479415

    Тогда можно до выхода на ПНР определиться с алгоритмом.

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

    Вот я попробовал наивный метод вычисления ряда Тейлора, потом пересчётом аргумента с последующим пересчётом результата.
    Теперь есть ещё идея по повышению точности - привести исходный x к диапазону 0...Pi/2 или (-Pi/4...+Pi/4) и вычислять sin или cos (что получиться после приведения) и тем самым повысить точность.

  10. #620

    По умолчанию

    kondor3000.
    Функция SIN_Rad для углов 90, 270 градусов (после пересчета в радианы) вместо верного результата выдает ноль. Одно из «магических» значений аргумента 1,5707964. При изменении последней цифры в любую сторону – все нормально. Может это только у меня. (OWEN Logic 2.6.347.0, на OWEN Logic 1.23 тоже самое)

Страница 62 из 64 ПерваяПервая ... 12526061626364 ПоследняяПоследняя

Похожие темы

  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

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

Ваши права

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