Reputation: 21500
using System;
public class Test
{
public static void Main()
{
Console.WriteLine(Math.Cos(1e27));
}
}
Starting from some value Math.Cos
and Math.Sin
are starting to return their argument instead of value inside of [-1, +1] segment. For example above the output is
1E+27
Why it is so and is it possible to fix such behaviour?
Upvotes: 3
Views: 333
Reputation: 186823
Trigonometric functions (Sin
, Cos
and alike) a periodic with a period of 2 pi (2 * Math.PI
). However, since Math.PI
is double
and thus has 16-17 correct digits only you can't compute 1e27
by naive (we require at least 27 digits):
Double result = Math.Cos(1e27); // totally wrong.
To perform the task, you need first appropriate pi
value, e.g. from here
And we have to work with BigInteger
as well:
int exponent = 27;
const String piString = "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054";
BigInteger pi2 = BigInteger.Parse(piString) * 2;
int scale = piString.Length - 1; // 2 * pi is not that big integer, it's 6.28...
BigInteger argument = BigInteger.Pow(10, exponent + scale);
// Now let's compute the double value (fraction) that's reminder of 1e27 % (2 * PI)
String remainder = (argument % pi2)
.ToString()
.PadLeft(piString.Length, '0')
.Insert(1, "."); // we can have x.xxx, 0.xxx, 0.0xxx etc. remainders
Double fraction =
Double.Parse("0." + remainder.Substring(0, 20), CultureInfo.InvariantCulture);
// and now, finally
Double result = Math.Cos(fraction);
Finally, the actual result is
-0.695977596990354
Note, that naive C++ computation (taken from Qwertiy comment which is 0.849247
) is totally wrong, since even C++ double
doesn't support pi
27 digits.
Upvotes: 4
Reputation: 2309
From the MSDN documentation:
The angle, a, must be in radians. Multiply by Math.PI/180 to convert degrees to radians.
Acceptable values of a range from approximately -9223372036854775295 to approximately 9223372036854775295. For values outside of this range, the Sin method returns a unchanged rather than throwing an exception.
Upvotes: 2
Reputation: 1659
The issue here is the math for Sin, Cos computation and the available number of bits to represent information stored, the real value.
To compute the Cos function, for example as you asked here, you need to compute this:
Cos(x) = 1 - x*x/2! + x*x*x*x/4! - x*x*x*x*x*x/6! + ...
If your number is at left(negative) or right(positive) limit borders of the data type used for storage of number, then you get fast into problems, because if you are near such limit, then computing x^2, x^4, x^6 is breaking outside of the data type limit pretty fast...
Sure, there are ways to chose some less violent algorithms to compute Sin/Cos, but there are limits to that.
The second thing here is, that for a longer time, math computations are thanks to behaviour like this easy and prone to errors. If the method does not mind to compute the result, it should end with an exception to provide information, where it failed. I saw many algorithms, (some were mine :) ), where such details were overlooked, producing magical results, and it ends up with NaN's or like here, returning back same value. It gets pretty hard to hunt these math errors with numbers, limits, etc in complex algorithms...
Upvotes: 0
Reputation: 763
"The angle, d, must be in radians. Multiply by Math.PI/180 to convert degrees to radians. Acceptable values of d range from approximately -9223372036854775295 to approximately 9223372036854775295. For values outside this range, the Cos method returns d unchanged rather than throwing an exception."
ref -> https://msdn.microsoft.com/en-us/library/system.math.cos(v=vs.110).aspx
Upvotes: 0