2015-10-14 6 views
3

http://ideone.com/XjMJBaКак исправить математические функции

using System; 

public class Test 
{ 
    public static void Main() 
    { 
     Console.WriteLine(Math.Cos(1e27)); 
    } 
} 

Начиная с некоторого значения Math.Cos и Math.Sin начинают возвращать свои аргументы вместо значения внутри [-1, +1] сегмента. Например, над выходом

1E+27 

Почему это так, и можно ли исправить такое поведение?

+12

Точно как [задокументировано] (https://msdn.microsoft.com/en-us/library/system.math. cos (v = vs.110) .aspx): «Допустимые значения * d * варьируются от приблизительно -9223372036854775295 до приблизительно 9223372036854775295. Для значений вне этого диапазона метод Cos возвращает * d * неизменным, а не бросает исключение». –

+0

Вы не можете «исправить» исходные методы, поскольку они были задокументированы так, чтобы они функционировали следующим образом. Тем не менее, вы можете создать свои собственные методы Cos и Sin, которые больше походят на то, что вы ожидаете. –

+1

@ LasseV.Karlsen - вам также придется вводить другой тип данных, чем 'double' здесь. Я подозреваю, почему они не поддерживают более высокие значения, вы уже находитесь в точке, где отсутствие точности означает, что разные между последовательными представляемыми значениями больше, чем π –

ответ

4

тригонометрические функции (Sin, Cos и тому подобное) периодическая с периодом 2 пи (2 * Math.PI). Однако, поскольку Math.PI является double и, таким образом, имеет 16-17 правильные цифры только вы не можете вычислить 1e27 наивным (мы требуем, по крайней мере, 27 цифр):

Double result = Math.Cos(1e27); // totally wrong. 

Чтобы выполнить задачу, необходимо первое соответствующее значение pi, например отсюда

http://www.piday.org/million/

И мы должны работать с BigInteger, а также:

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

Наконец, фактический результат

-0,695977596990354

Примечание, что naive C++ вычисление (взято из Qwertiy комментарий, который является 0.849247), является абсолютно неправильным, поскольку даже C++ double не поддерживает pi 27 цифр.

+0

[Wolfram] (http://www.wolframalpha.com/input/?i=cos%281000000000000000000000000000%29&a=TrigRD_R) не согласны с обе степени. И вы игнорируете разницу между 1e27 и ее представлением в двойном. В любом случае, спасибо за эту идею. – Qwertiy

+0

Извините, но * Wolfram * в отличие от 'Math.Cos (1e27)' вычисляет Cos (1e27 * градусов *) не * радиан *. Вот почему у вас есть эта разница. –

+0

Нет, я изменил устройство - есть строка ввода ниже. – Qwertiy

2

От MSDN documentation:

угол, а, должно быть в радианах. Умножьте Math.PI/180 для преобразования градусов в радианы.

Приемлемые значения в диапазоне от приблизительно -9223372036854775295 приблизительно 9223372036854775295. Для значений вне этого диапазона , метод Син возвращает неизменным, а не бросать исключение.

+0

Почему это было сделано таким образом? Например, C++ вычисляет значение: http://codepad.org/8QbS9HPC – Qwertiy

0

«Угол, д, должен быть в радианах. Умножить на Math.PI/180 для преобразования градусов в радианы. Приемлемые значения г диапазоне от приблизительно -9223372036854775295 до приблизительно 9223372036854775295. Для значений вне этого диапазона, метод Cos возвращает d без изменений, а не бросает исключение ».

исх ->https://msdn.microsoft.com/en-us/library/system.math.cos(v=vs.110).aspx

0

Проблема заключается в математике расчета Sin, Cos и доступном количестве бит для представления сохраненной информации, реальной стоимости.

Для вычисления функции Cos, например, как вы просили здесь, вам нужно вычислить это:

Cos(x) = 1 - x*x/2! + x*x*x*x/4! - x*x*x*x*x*x/6! + ... 

Если ваш номер находится на левой (отрицательной) или вправо (положительный) предельных границ типа данных используется для хранения числа, то вы быстро входите в проблемы, потому что, если вы находитесь рядом с таким пределом, то вычисление x^2, x^4, x^6 очень быстро нарушает пределы типа данных ...

Конечно, есть способы выбрать несколько менее жестоких алгоритмов для вычисления Sin/Cos, но есть ограничения на это.

Вторая вещь здесь, что в течение более длительного времени математические вычисления являются результатом такого поведения, как это легко и подвержено ошибкам. Если метод не возражает против вычисления результата, он должен закончиться исключением, чтобы предоставить информацию, где она не удалась. Я видел много алгоритмов (некоторые из них были моими :)), где такие детали были упущены, производя магические результаты, и это заканчивается с NaN или как здесь, возвращая ту же самую ценность. Очень сложно искать эти математические ошибки с числами, пределами и т. Д. В сложных алгоритмах ...

Смежные вопросы