Почему эта функция C# используется для рисования синусоиды с использованием переполнения System.Drawing?

Итак, я сделал эту маленькую программу для создания окон, которая рисует синусоидальную волну, но когда я ставлю такие функции, sin(x) ^ x это переполняется. Пожалуйста, просмотрите этот код и помогите мне разобраться!

    private void button1_Click(object sender, EventArgs e)
    {
        if (Completed) return;
        Completed = true;
        Graphics g = this.CreateGraphics();
        Pen pen = new Pen(Brushes.Black, 2.0F);

        float x1 = 0;
        float y1 = 0;

        float y2 = 0;

        float yEx = 300;
        float eF = 50;


        for (float x = 0; x < Width; x += 0.005F)
        {
            y2 = (float)Math.Pow(Math.Sin(x) , x);

            g.DrawLine(pen, x1 * eF, y1 * eF + yEx, x * eF, y2 * eF + yEx);
            x1 = x;
            y1 = y2;
        }

    }

Когда я выполняю, он работает некоторое время, а затем отображает это:
что именно отображается

Любой вклад приветствуется!

3 ответа

Решение

Хорошо, получается, что решение представляет собой смесь ответа TyCobb и ответа Джона Ву. Когда вы используете Math.Sin на возрастающей стоимости, как xпримерно половина выхода из Math.Sin(x) будет отрицательным. Math.Pow не нравится, когда вы задаете отрицательное число в первом параметре и нецелое число в качестве второго параметра (он вернет NaN в этом случае). Причина этого в том, что когда вы поднимаете отрицательное число нецелым числом, результатом является комплексное число.

Сейчас Graphics.DrawLine не волнует, если вы проходите NaN как любая из второй пары float параметры (x2, y2) как он просто вернется, ничего не делая. Но если вы пройдете NaN как одна из первой пары (x1, y1), он бросит OverflowException, Почему это так, я не уверен, но я бы хотел догадаться, что это был недосмотр со стороны команды.NET в те времена.

Теперь, с первого открытия, вы можете увидеть, что проблема в основном всякий раз, когда x отрицательно. Простое решение этого было бы заменить x с Math.Pow(x), но это может изменить вывод таким образом, который не отражает намерения исходной функции. Вторым решением было бы просто пропустить эти итерации, но это оставит дыры в вашем графике.

Третье решение, которое вы уже догадались, но это замена Math.Pow с Complex.Pow, Это обеспечит вам необходимую поддержку для потенциальных входов, которые вы можете получить для своей функции мощности, возвращая комплексное число. Как только вы это сделаете, вы можете выбрать, что вы хотите сделать с результатом. (Я просто взял Real часть результата как мой y и отбросил Imaginary часть.)

for (float x = 0; x < Width; x += 0.005F)
{
    y2 = (float)Complex.Pow(Math.Sin(x), x).Real;

    g.DrawLine(pen, x1 * eF, y1 * eF + yEx, x * eF, y2 * eF + yEx);
    x1 = x;
    y1 = y2;
}

Результат таков:

введите описание изображения здесь

Проблема в том, что Math.Pow иногда возвращает число с плавающей запятой, которое не является числом или NaN. В частности:

x <0, но не NegativeInfinity; y не является целым числом, NegativeInfinity или PositiveInfinity. = NaN

Поскольку Sin(x) нарисует кривую, которая выше и ниже 0, некоторые ее значения будут отрицательными, поэтому Math.Pow колл даст NaN, который, конечно, нельзя нарисовать в декартовом пространстве.

Я предлагаю вам временно изменить код следующим образом: оберните вызов draw блоком try.

try
{
    g.DrawLine(pen, x1 * eF, y1 * eF + yEx, x * eF, y2 * eF + yEx);
}
catch {}  //Not usually a great idea to eat all exceptions, but for troubleshooting purposes this'll do

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

Подтвердите свои значения для DrawLine,

Вы делаете проверку для Width в цикле, но вы не проверяете свои вычисления перед вызовом DrawLine, Вот пример без ничьей:

float x1 = 0;
float y1 = 0;

float y2 = 0;

float yEx = 300;
float eF = 50;

const int width = 1024;
const int height = 1024;

for (float x = 0; x < width; x += 0.005F)
{
    y2 = (float)Math.Pow(Math.Sin(x) , x);

    var a = x1 * eF;
    var b = y1 * eF + yEx; 
    var c = x * eF;
    var d = y2 * eF + yEx;

    if(a < 0 || a >= width || b < 0 || b >= height || c < 0 || c >= width || d < 0 || d > height)
        Console.WriteLine("Snap! {0} | {1} | {2} | {3}", a, b, c, d);
    x1 = x;
    y1 = y2;
}

Вот онлайн пример

GDI не поможет вам, когда вы попытаетесь нарисовать холст, на самом деле он, как правило, будет бум. Вы всегда должны ограничивать значения в пределах высоты и ширины.

Вот некоторые основные моменты примера:

Snap! 1101,215 | NaN | 1101,465 | NaN
Snap! 1101,465 | NaN | 1101,715 | NaN
Snap! 1101,715 | NaN | 1101,965 | NaN
Snap! 1101,965 | NaN | 1102,215 | NaN
Snap! 1102,215 | NaN | 1102,465 | NaN
Snap! 1102,465 | NaN | 1102,715 | NaN
Snap! 1102,715 | NaN | 1102,965 | NaN
Snap! 1102,965 | NaN | 1103,215 | NaN
Snap! 1103,215 | NaN | 1103,465 | NaN
Snap! 1103,465 | NaN | 1103,715 | NaN
Snap! 1103,715 | NaN | 1103,965 | NaN
Snap! 1103,965 | NaN | 1104,215 | NaN
Snap! 1104,215 | NaN | 1104,465 | NaN
Snap! 1104,465 | NaN | 1104,715 | NaN
Snap! 1104,715 | NaN | 1104,965 | NaN
Snap! 1104,965 | NaN | 1105,215 | NaN
Другие вопросы по тегам