Math.Tan() около -Pi/2 неправильно в .NET, верно в Java?

У меня сбой модульного теста на Math.Tan(-PI/2), возвращающем неправильную версию в .NET.

«Ожидаемое» значение берется из Wolfram online (с использованием прописанной константы для -Pi/2). Убедитесь сами здесь.

Как правильно заметили в комментариях, математический результат tan(-pi/2) равен бесконечности. Однако константа Math.PI не совсем точно представляет PI, так что это входные данные, близкие к пределу.

Вот код.

double MINUS_HALF_PI = -1.570796326794896557998981734272d;
Console.WriteLine(MINUS_HALF_PI == -Math.PI/2); //just checking...

double tan = Math.Tan(MINUS_HALF_PI);
Console.WriteLine("DotNET  {0:E20}", tan);

double expected = -1.633123935319534506380133589474e16;
Console.WriteLine("Wolfram {0:E20}", expected);

double off = Math.Abs(tan-expected);
Console.WriteLine("         {0:E20}", off);

Вот что печатается:

True
DotNET  -1.63317787283838440000E+016
Wolfram -1.63312393531953460000E+016
         5.39375188498000000000E+011

Я думал, что это проблема представления с плавающей запятой.

Как ни странно, то же самое в Java ДЕЙСТВИТЕЛЬНО возвращает то же значение, что и Wolfram, вплоть до последней цифры — см. оценку в Eclipse. (Выражения обрезаны — поверьте мне, они используют ту же константу, что и MINUS_HALF_PI выше.)

введите здесь описание изображения

True
DotNET  -1.63317787283838440000E+016
Wolfram -1.63312393531953460000E+016
Java    -1.63312393531953700000E+016

Как видите, разница в следующем:

  • между Wolfram и .NET: ~5.39 * 10^11
  • между Wolfram и Java: =2.40 * 10^1

Это десять порядков!

Итак, есть идеи, почему реализации .NET и Java так сильно различаются? Я ожидаю, что они оба просто переложат фактические вычисления на процессор. Является ли это предположение нереалистичным для x86?

Обновлять

В соответствии с просьбой я попытался запустить Java с strictfp. Без изменений:

введите здесь описание изображения


person Cristian Diaconescu    schedule 29.11.2013    source источник
comment
Во-первых, Math.Tan() принимает на вход только значение типа double. Во-вторых, я не думаю, что decimal может помочь в этом отношении. В-третьих, Java и .NET используют одно и то же стандартное представление для doubles, поэтому возможность представления значения вряд ли будет проблемой.   -  person Cristian Diaconescu    schedule 29.11.2013
comment
вы правы, Math.Tan не может работать с десятичными числами   -  person nabuchodonossor    schedule 29.11.2013
comment
Вы пробовали бегать с strictfp?   -  person chrylis -cautiouslyoptimistic-    schedule 29.11.2013
comment
Если я не забыл всю свою тригонометрию, тангенс (-Pi/2) равен +/- бесконечности. Цифры, что разные реализации будут вести себя непредсказуемо в этой области, когда начнут летать ошибки округления. Интересно, что именно отличается между реализациями, чтобы вызвать эти разные результаты.   -  person BambooleanLogic    schedule 29.11.2013
comment
Возможные причины: экстремальная оптимизация. com/Блог/index.php/2011/02/   -  person nemesv    schedule 29.11.2013
comment
@chrylis Я сделал это сейчас - смотрите обновление.   -  person Cristian Diaconescu    schedule 29.11.2013
comment
Tan равно -Infinity при -Pi/2, поэтому я думаю, что это не репрезентативное значение для проверки точности используемой функции. Конечно, ошибка составляет десять порядков, но это все еще относительная ошибка всего 0,003%. Цифра только кажется большой, но на самом деле это не так.   -  person user1983983    schedule 29.11.2013
comment
@nemesv Интересная статья. Но он описывает ошибки, которые появляются для больших входных данных (углы намного больше 360 градусов). У меня здесь проблема с большим выходом.   -  person Cristian Diaconescu    schedule 29.11.2013
comment
Так откуда взялось значение для MINUS_HALF_PI? Очевидно, что это не Wolfram Alpha   -  person Martin Smith    schedule 29.11.2013


Ответы (1)


Весь вопрос построен для создания тенденциозного результата. Ближайшее к половине PI значение double равно -1.5707963267948966; остальные цифры просто игнорируются. Поэтому неудивительно, что ни C#, ни Java не обнаруживают, что оставшиеся 14 дополнительных цифр не приближают результат к -PI/2, а тщательно выбираются, чтобы обмануть Wolfram Alpha и вернуть значение, близкое к результату Java.

-1.570796326794896557998981734272 // the number from the question
-1.57079632679489661923132169163975… // the real digits of -PI/2
                  ↑
                the end of the double precision

Любое другое число в диапазоне, которое будет округлено до того же double числа, включая точное двойное значение, используемое в Java, уступает значению в Wolfram Alpha, не имеющему ничего общего ни с результатом C#, ни с Java.

person Holger    schedule 29.11.2013
comment
Хорошо поймал. Интересно посмотреть, что говорит ОП! - person Martin Smith; 29.11.2013
comment
ОП говорит, интересно! Мне пришло в голову, что эта константа имеет слишком много цифр, но я проверил ее на Math.PI/2 и, к удивлению (нет!), они совпадают (в двоичном представлении double 64, то есть...). Мне не приходило в голову, что «бесполезные» дополнительные цифры будут иметь значение в Wolfram. Я действительно не знаю, как появилась эта сверхдлинная константа (мне нужно было перенести существующий код Java на C #), но это действительно похоже на то, что это было перепроектировано! :) Я посмотрю историю исходного кода в понедельник и опубликую обновление, если найду что-нибудь интересное. - person Cristian Diaconescu; 30.11.2013
comment
В качестве побочного вопроса, учитывая двойное число, как мне получить диапазон (действительных чисел) значений, которые будут приближаться (округляться) к этому двойному? Я думаю о добавлении и вычитании «1» из двоичного представления мантиссы значения и использовании этих значений в качестве (исключительных) границ интервала. Но, может быть, есть более простой способ, чем смотреть на биты? - person Cristian Diaconescu; 30.11.2013
comment
В Java Math.ulp(value) возвращает расстояние до следующего double. Таким образом, в основном value±ulp будет тем диапазоном, который сопоставляется со значением. Но, конечно, этот диапазон не может быть оценен с использованием double значений. - person Holger; 30.11.2013
comment
Похоже, что в .NET BCL нет ничего подобного, но здесь — простой способ получить тот же результат. Это в духе моего предыдущего комментария. - person Cristian Diaconescu; 30.11.2013
comment
Что касается вашего ответа - я больше изучил его, и есть небольшой поворот. Теперь вы правы в том, что последние цифры спорны с точки зрения компилятора. Но возникает вопрос: какое значение из диапазона значений, приближенных к этому двоичному представлению, я должен передать в Wolfram? Оказывается, преобразование двоичного представления в десятичное, '-1,5707963267948966' является приблизительным. Более точное десятичное число ЯВЛЯЕТСЯ исходным, слишком длинное постоянное - см. здесь - person Cristian Diaconescu; 30.11.2013
comment
[продолжение] ...и мне кажется правильным скормить «середину диапазона» Вольфраму. Итак... вернемся к квадрату 1? - person Cristian Diaconescu; 30.11.2013