2017-01-13 19 views
0

Итак, я сделал эту небольшую программу окон, которая рисует синусоидальную волну, но когда я помещаю некоторые функции, такие как sin(x)^x, она переполняется. Просмотрите этот код и помогите мне разобраться!Почему эта функция C# для рисования синусоиды с использованием переполнения System.Drawing?

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; 
     } 

    } 

Когда я исполняю, он работает немного, и затем отображает это:
what is exactly displayed

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

+0

Максимальное значение для 'float' является так же, как 'Int32'. Если результат превышает 2,147,483,647, вы получите ошибку переполнения. Вы пробовали использовать 'double' (что такое же максимальное значение, как' Int64' - 9,223,372,036,854,775,807)? Или проверите значения для обработки переполнения. – Tim

+1

Вы не проверяете свои входы в ручку. Вы должны убедиться, что они находятся в пределах холста, потому что на самом деле это не похоже на рисование в ничто. – TyCobb

+0

И что такое значение 'Width'? Отбрасывая это в Visual Studio и используя значение 1920, тогда печать 'y2' дает мне много результатов (многие из них« NaN'), но я никогда не получаю переполнение. И это имеет смысл, на самом деле - диапазон 'sin (x)^x' будет ограничен (-1, 1), поэтому он никогда не должен переполняться. – Abion47

ответ

1

Хорошо, так получилось, что решение представляет собой сочетание ответа 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; 
} 

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

enter image description here

+0

очень приятно. не знал. NET имеет сложный класс для этого. –

+0

Просто Браво, друг мой. Действительно полезно. Я бы не догадался, что у C# есть способ справиться с этим с помощью Complex.Pow. Благодаря! –

0

Подтвердите свои значения для 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; 
} 

Here's an online example

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 
+0

Замена внутренняя часть цикла for с помощью g.DrawLine (Pens.Black, -1, -1, 1200, 800); '(в битмапе размером 1024x768) отлично работает. Я не думаю, что отключение ребра canvas - проблема в этом случае. – Abion47

+0

@ Abion47 Я обновил свой пример и демонстрацию кода. – TyCobb

0

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

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

Так как sin (х) будет нарисовать кривую, как выше, так и ниже 0, некоторые из его значений будет отрицательным, таким образом, Math.Pow вызов будет давать NaN, который, конечно, не может быть обращено на декартовой пространстве ,

Предлагаю временно изменить код следующим образом: Оберните вызов рисования с помощью блока 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.

+0

Использование 'NaN' в качестве входа для метода Graphics.DrawX не будет работать, но оно также не будет генерировать исключение, он просто ничего не делает. – Abion47

+0

Говоря о негативах, GDI также будет бросать исключение из памяти, когда вы проходите отрицательные числа. Это забавные времена отладки, когда вы считаете, что у вас законно закончились память.;) – TyCobb