HighTechTalks DotNet Forums  

Bad math when using * operator along with Math.pow

Dotnet VJSharp microsoft.public.dotnet.vjsharp


Discuss Bad math when using * operator along with Math.pow in the Dotnet VJSharp forum.



Reply
 
Thread Tools Search this Thread Display Modes
  #1  
Old   
webmaster
 
Posts: n/a

Default Bad math when using * operator along with Math.pow - 08-14-2004 , 03:19 AM






Check out this code. I need to be able to multiply a 15 digit decimal number by 10 to the power of 15, and come up with the correct answer. Easy enough for other languages to do, but J# seems to choke on it. Here's the code.

private void button1_Click (Object sender, System.EventArgs e)
{
double dblPi = 3.14159265358979;
double dblTheta = 121;
double value = System.Math.Sin( (dblTheta / 180) * dblPi );
System.Console.Write(value);
System.Console.Write("\n");
System.Console.Write(Math.pow(10, 15) * value);

// Should return 857167300702113
// but instead returns 857167300702114
}

My best guess is that it has something to do with the * operator. My guess is that it does some kind of crazy rounding, but looking at the code it doesn't make sense really. I made sure that the original value comes out right, and it does, and I trust the Math.pow function to come out right as well, but the combination of it all fails.

I'm so puzzled. HELP!

- Nick

************************************************** ********************
Sent via Fuzzy Software @ http://www.fuzzysoftware.com/
Comprehensive, categorised, searchable collection of links to ASP & ASP.NET resources...

Reply With Quote
  #2  
Old   
Ianier Munoz
 
Posts: n/a

Default Re: Bad math when using * operator along with Math.pow - 08-15-2004 , 11:18 AM






Hi Nick,

FYI, if you change the first line of your code by

double dblPi = System.Math.PI;

then it returns 857167300702112

This behavior seems to do with how the compiler translates double constants
to their binary representation.
Regards,
--
Ianier Munoz
http://www.chronotron.com


"Nick Hauenstein" <webmaster (AT) nahnet (DOT) com> wrote

Quote:
Check out this code. I need to be able to multiply a 15 digit decimal
number by 10 to the power of 15, and come up with the correct answer. Easy
enough for other languages to do, but J# seems to choke on it. Here's the
code.

private void button1_Click (Object sender, System.EventArgs e)
{
double dblPi = 3.14159265358979;
double dblTheta = 121;
double value = System.Math.Sin( (dblTheta / 180) * dblPi );
System.Console.Write(value);
System.Console.Write("\n");
System.Console.Write(Math.pow(10, 15) * value);

// Should return 857167300702113
// but instead returns 857167300702114
}

My best guess is that it has something to do with the * operator. My guess
is that it does some kind of crazy rounding, but looking at the code it
doesn't make sense really. I made sure that the original value comes out
right, and it does, and I trust the Math.pow function to come out right as
well, but the combination of it all fails.

I'm so puzzled. HELP!

- Nick

************************************************** ********************
Sent via Fuzzy Software @ http://www.fuzzysoftware.com/
Comprehensive, categorised, searchable collection of links to ASP &
ASP.NET resources...



Reply With Quote
  #3  
Old   
Jay B. Harlow [MVP - Outlook]
 
Posts: n/a

Default Re: Bad math when using * operator along with Math.pow - 08-15-2004 , 12:50 PM



Nick,
In addition to Ianier's comments. See the following on how doubles in .NET
really work.

http://www.yoda.arachsys.com/csharp/floatingpoint.html

http://www.yoda.arachsys.com/csharp/decimal.html

Hope this helps
Jay

"Nick Hauenstein" <webmaster (AT) nahnet (DOT) com> wrote

Quote:
Check out this code. I need to be able to multiply a 15 digit decimal
number by 10 to the power of 15, and come up with the correct answer. Easy
enough for other languages to do, but J# seems to choke on it. Here's the
code.
Quote:
private void button1_Click (Object sender, System.EventArgs e)
{
double dblPi = 3.14159265358979;
double dblTheta = 121;
double value = System.Math.Sin( (dblTheta / 180) * dblPi );
System.Console.Write(value);
System.Console.Write("\n");
System.Console.Write(Math.pow(10, 15) * value);

// Should return 857167300702113
// but instead returns 857167300702114
}

My best guess is that it has something to do with the * operator. My guess
is that it does some kind of crazy rounding, but looking at the code it
doesn't make sense really. I made sure that the original value comes out
right, and it does, and I trust the Math.pow function to come out right as
well, but the combination of it all fails.
Quote:
I'm so puzzled. HELP!

- Nick

************************************************** ********************
Sent via Fuzzy Software @ http://www.fuzzysoftware.com/
Comprehensive, categorised, searchable collection of links to ASP &
ASP.NET resources...




Reply With Quote
  #4  
Old   
Nick Hauenstein
 
Posts: n/a

Default Re: Bad math when using * operator along with Math.pow - 08-15-2004 , 05:05 PM



Thanks so much! I now can see what causes the code to freak out. However, I
wonder if there is a way to code around this behavior, or to deal with it.
Let me explain the importance first.

The code itself is some test code I wrote up while searching out an elusive
bug in a Pseudo-Random number generator for an encryption program. I need
to be able to produce exactly the same random sets of numbers that other
implementations of the generator produce so the encrypted data can be
decrypted no matter who's implementation of the algorithm is used.

I'm basing the J# version off a VB6 implementation, interestingly enough,
and I want to ensure it is compatible with that. Thus Pi must be defined as
it is in the code, because that's the precision to which it was represented
also in the VB6 code. VB6 does the calculations as I'd expect without
issue.

The part where the J# code goes hey-wire is in the custom rounding routine
(to get around .NET's banker's rounding). The reason for the rounding
function is the results of the sin & cos functions have to be rounded to 15
digits to again match the precision in other implementations.

*****Another thing that could eliminate this whole mess would be to get a
reliable rounding function that can handle the data I throw at it.*****

If you can figure this out, I'll name my firstborn after you!

But if anyone feels like sifting through the code, here's the code for the
rounding function:

public static double OldRound(double value, int digits)
{

int sign = System.Math.Sign(value);
double scale = Math.pow(10, digits);
double round = System.Math.Abs(value);

// BAD MATH RESULTS FROM THIS CALCULATION:
round = round * scale;

round = round + .5;
round = Math.floor(round);
round = round / scale;
round = sign * round;
return round;
}

If you're interested, here's the bit of code that calls the rounding
function. Ignore any weird variable names (like sngPi when it's the double
type), I just used all the same variable names as the VB implementation to
keep everything straight when I was writing the port.

private static double dblCenterY;
private static double dblCenterX;

// It is very important that these numbers are EXACTLY the same
// in ALL implementations to allow universal compatibility.
// MAGIC NUMBERS
// KEEP PI TO THIS PRECISION, NO MORE, NO LESS
private static double sngPi = 3.14159265358979;

private static void Generate(double dblRadius, double dblTheta)
{
double sngMaxUpper = 2147483647;
double sngMaxLower = -2147483648;

// Basically what we're doing here is picking a point on a circle
// centered at (dblCenterX, dblCenterY). This point will serve as
// the center of the next circle used for this function. The
// radius of the circle is given to the function, as well as the
// angle the new point makes with the center of the orignal circle.
// This angle and radius is ultimately what determines the new
// point, and serve as our pseudo-random seeds

double dblResultX;
double dblResultY;

dblResultX = (dblRadius * OldRound(System.Math.Cos((dblTheta / 180) *
sngPi), 15)) + dblCenterX;


dblResultY = (dblRadius * OldRound(System.Math.Sin((dblTheta / 180) *
sngPi), 15)) + dblCenterY;

if (dblResultX > sngMaxUpper || dblResultX < sngMaxLower)
{

// Re-center if new X coordinate is far off the boundary of the X-axis
ReCenter(dblCenterY, 0);

}
else
{

// Otherwise, calculate the new X-coordinate
dblCenterX = dblResultX;

}

if (dblResultY > sngMaxUpper || dblResultY < sngMaxLower)
{

// Re-center if new Y coordinate is far off the boundary of the Y-axis
ReCenter(0,dblCenterX);

}
else
{

// Otherwise, calculate the new Y-coordinate
dblCenterY = dblResultY;

}

}

Or if you don't want to sort through that mess, I'll just propose the
original problem (sample data all filled in, and simplified):

private void button1_Click (Object sender, System.EventArgs e)
{
double dblPi = 3.14159265358979;
double dblTheta = 121;
double value = System.Math.Sin( (dblTheta / 180) * dblPi );
System.Console.Write(value);
System.Console.Write("\n");
System.Console.Write(Math.pow(10, 15) * value);

// Should return 857167300702113
// but instead returns 857167300702114
}

What can I do to code around this?

Thanks again so much, and thanks in advance to anyone who replies or solves
this...
- Nick

Reply With Quote
  #5  
Old   
Nick Hauenstein
 
Posts: n/a

Default Re: Bad math when using * operator along with Math.pow - 08-15-2004 , 05:22 PM



Is there anything in J# equivalent to the toFixed() method in JScript?

Because I was playing around with JScript and this works EXACTLY like I need
the other to work:

var sngPi = 3.14159265358979;
document.write(Math.sin((121 / 180) * sngPi).toFixed(15));

Again, and as always, thanks in advance!

- Nick

Reply With Quote
  #6  
Old   
Ianier Munoz
 
Posts: n/a

Default Re: Bad math when using * operator along with Math.pow - 08-15-2004 , 11:52 PM



java.math.BigDecimal will give you some more control over precision.
However, you'll have to implement the trig functions by yourself ( e.g.
sin(x) = x - x^3/3! + x^5/5! - x^7/7! ... +/- x^n/n! )
Regards,
--
Ianier Munoz
http://www.chronotron.com


"Nick Hauenstein" <root (AT) spudge (DOT) com> wrote

Quote:
Thanks so much! I now can see what causes the code to freak out. However,
I
wonder if there is a way to code around this behavior, or to deal with it.
Let me explain the importance first.

The code itself is some test code I wrote up while searching out an
elusive
bug in a Pseudo-Random number generator for an encryption program. I need
to be able to produce exactly the same random sets of numbers that other
implementations of the generator produce so the encrypted data can be
decrypted no matter who's implementation of the algorithm is used.

I'm basing the J# version off a VB6 implementation, interestingly enough,
and I want to ensure it is compatible with that. Thus Pi must be defined
as
it is in the code, because that's the precision to which it was
represented
also in the VB6 code. VB6 does the calculations as I'd expect without
issue.

The part where the J# code goes hey-wire is in the custom rounding routine
(to get around .NET's banker's rounding). The reason for the rounding
function is the results of the sin & cos functions have to be rounded to
15
digits to again match the precision in other implementations.

*****Another thing that could eliminate this whole mess would be to get a
reliable rounding function that can handle the data I throw at it.*****

If you can figure this out, I'll name my firstborn after you!

But if anyone feels like sifting through the code, here's the code for the
rounding function:

public static double OldRound(double value, int digits)
{

int sign = System.Math.Sign(value);
double scale = Math.pow(10, digits);
double round = System.Math.Abs(value);

// BAD MATH RESULTS FROM THIS CALCULATION:
round = round * scale;

round = round + .5;
round = Math.floor(round);
round = round / scale;
round = sign * round;
return round;
}

If you're interested, here's the bit of code that calls the rounding
function. Ignore any weird variable names (like sngPi when it's the double
type), I just used all the same variable names as the VB implementation to
keep everything straight when I was writing the port.

private static double dblCenterY;
private static double dblCenterX;

// It is very important that these numbers are EXACTLY the same
// in ALL implementations to allow universal compatibility.
// MAGIC NUMBERS
// KEEP PI TO THIS PRECISION, NO MORE, NO LESS
private static double sngPi = 3.14159265358979;

private static void Generate(double dblRadius, double dblTheta)
{
double sngMaxUpper = 2147483647;
double sngMaxLower = -2147483648;

// Basically what we're doing here is picking a point on a
circle
// centered at (dblCenterX, dblCenterY). This point will
serve as
// the center of the next circle used for this function.
The
// radius of the circle is given to the function, as well
as the
// angle the new point makes with the center of the orignal
circle.
// This angle and radius is ultimately what determines the
new
// point, and serve as our pseudo-random seeds

double dblResultX;
double dblResultY;

dblResultX = (dblRadius *
OldRound(System.Math.Cos((dblTheta / 180) *
sngPi), 15)) + dblCenterX;


dblResultY = (dblRadius *
OldRound(System.Math.Sin((dblTheta / 180) *
sngPi), 15)) + dblCenterY;

if (dblResultX > sngMaxUpper || dblResultX < sngMaxLower)
{

// Re-center if new X coordinate is far off the
boundary of the X-axis
ReCenter(dblCenterY, 0);

}
else
{

// Otherwise, calculate the new X-coordinate
dblCenterX = dblResultX;

}

if (dblResultY > sngMaxUpper || dblResultY < sngMaxLower)
{

// Re-center if new Y coordinate is far off the
boundary of the Y-axis
ReCenter(0,dblCenterX);

}
else
{

// Otherwise, calculate the new Y-coordinate
dblCenterY = dblResultY;

}

}

Or if you don't want to sort through that mess, I'll just propose the
original problem (sample data all filled in, and simplified):

private void button1_Click (Object sender, System.EventArgs e)
{
double dblPi = 3.14159265358979;
double dblTheta = 121;
double value = System.Math.Sin( (dblTheta / 180) * dblPi );
System.Console.Write(value);
System.Console.Write("\n");
System.Console.Write(Math.pow(10, 15) * value);

// Should return 857167300702113
// but instead returns 857167300702114
}

What can I do to code around this?

Thanks again so much, and thanks in advance to anyone who replies or
solves
this...
- Nick



Reply With Quote
Reply




Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off



Powered by vBulletin Version 3.5.4
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.