Reputation: 43
So I made this little windows forms program that draws a sine wave but when I put certain functions like sin(x) ^ x
it overflows.
Please review this code and help me figure this out!
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;
}
}
When I execute, it runs for a bit and then displays this:
Any contribution is appreciated!
Upvotes: 1
Views: 6481
Reputation: 24726
Alright, so it turns out the solution is a mix of TyCobb's answer and John Wu's answer. When you use Math.Sin
on an increasing value like x
, roughly half of the output from Math.Sin(x)
is going to be negative. Math.Pow
doesn't like it when you give it a negative number in the first parameter and a non-integer as the second parameter (it will return NaN
in this case). The reason for this is because when you raise a negative number by a non-integer, the result is a complex number.
Now Graphics.DrawLine
doesn't care if you pass NaN
as either of the second pair of float
parameters (x2, y2
) as it will just return without doing anything. But if you pass NaN
as one of the first pair (x1, y1
), it will throw an OverflowException
. Why it does this, I'm not sure, but I'd be willing to guess that it was an oversight on the part of the .NET team back in the day.
Now from the first discovery, you can see the problem is mainly whenever x
is negative. The easy solution to that would be to replace x
with Math.Pow(x)
, but that might alter the output in a way that doesn't reflect the intentions of the original function. A second solution would be to simply skip those iterations, but that will leave holes in your graph.
A third solution you may have guessed already, but that is to replace Math.Pow
with Complex.Pow
. This will give you the support you need for the potential inputs you may get to your power function by returning a complex number. Once you've done that, you can choose what you want to do with the result. (I just took the Real
part of the result as my y
and discarded the Imaginary
portion.)
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;
}
The result is this:
Upvotes: 2
Reputation: 52280
The problem is that Math.Pow sometimes returns a float that is not a number or NaN. Specifically:
x < 0 but not NegativeInfinity; y is not an integer, NegativeInfinity, or PositiveInfinity. = NaN
Since Sin(x) will draw a curve that is both above and below 0, some of its values will be negative, thus the Math.Pow
call will yield NaN, which of course cannot be drawn on Cartesian space.
I suggest you temporarily modify your code as follows: Wrap the draw call with a try block.
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
Then run your code and view the line. The breaks in the line will tell you which parts of the domain/range are problematic. Once you have a holistic view of the computational issues, you may be able to transform/map your Cartesian space into a domain where the overflows don't happen, e.g. by adding a constant to the result of the SIN function.
Upvotes: 0
Reputation: 9089
Validate your values for the DrawLine
.
You do a check for the Width
at the loop, but you do not validate your calculations before you call DrawLine
. Here's an example without the draw:
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 will not help you out when you try to draw off the canvas, in fact it will normally go boom. You should always limit the values to be within the height and width.
Here's some highlights of the example:
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
Upvotes: 0