Math pow в Java и C # возвращают немного разные результаты?

Я переношу программу с С# на java. Я столкнулся с тем, что

Ява

Math.pow(0.392156862745098,1./3.) = 0.7319587495200227

C#

Math.Pow( 0.392156862745098, 1.0 / 3.0) =0.73195874952002271

эта последняя цифра приводит к значительным различиям в дальнейших расчетах. Есть ли способ эмулировать pow С#?

спасибо


person Stepan Yakovenko    schedule 12.04.2012    source источник
comment
Ваш код не даст воспроизводимых результатов даже в .net, так что забудьте об этом. Связанный: stackoverflow.com/questions/6683059/   -  person CodesInChaos    schedule 13.04.2012
comment
Действительно? 17-я значащая цифра дает вам достаточную разницу в дальнейших расчетах? Можете ли вы привести пример?   -  person Bill the Lizard    schedule 13.04.2012
comment
Если вы сравните его с calc.exe и wolfram alpha, 1 все равно будет неправильным. Я бы придерживался реализации Java.   -  person Austin Salonen    schedule 13.04.2012
comment
То, что вы видите, известно как чувствительность к начальным условиям и является показателем того, что вы имеете дело с хаотической системой (en.wikipedia.org/wiki/). Так что, если вы не можете принять хаос...   -  person andand    schedule 13.04.2012
comment
@andand: То, что он видит, - это различия в том, как Java и C # печатают числа. В работе нет хаотичной системы.   -  person Stephen Canon    schedule 13.04.2012
comment
Результаты с плавающей запятой зависят от компилятора и процессора, java предоставляет StrictMath.pow() и ключевое слово strictfp, чтобы обеспечить одинаковые результаты для разных jvms и процессоров. К сожалению, в данном случае это мало поможет, так как C# не предоставляет такой функциональности.   -  person josefx    schedule 13.04.2012
comment
@Stephen Canon: я не имел в виду наблюдаемые различия между Java и C # (хотя, читая мой комментарий, я понимаю, как это можно сделать). Я имел в виду основную проблему, описанную в ОП, которая возникает из-за этой разницы. Если отличия от этого положения дел впоследствии приводят к большим и неприемлемым различиям, он имеет дело с проблемой, чувствительной к начальным условиям, что указывает на возможную хаотическую систему. Если ОП не может терпеть хаотический характер системы, требующей этих вычислений, (она) он усваивает неправильный урок.   -  person andand    schedule 13.04.2012


Ответы (6)


Просто чтобы подтвердить то, что написал Крис Шейн, я получаю те же двоичные значения:

// Java
public class Test
{
    public static void main(String[] args)
    {
        double input = 0.392156862745098;
        double pow = Math.pow(input, 1.0/3.0);            
        System.out.println(Double.doubleToLongBits(pow));
    }
}

// C#
using System;

public class Test
{
    static void Main()
    {
        double input = 0.392156862745098;
        double pow = Math.Pow(input, 1.0/3.0);            
        Console.WriteLine(BitConverter.DoubleToInt64Bits(pow));
    }
}

Вывод обоих: 4604768117848454313

Другими словами, двойные значения представляют собой точно один и тот же битовый шаблон, и любые различия, которые вы видите (при условии, что вы получите одинаковые результаты), скорее связаны с форматированием чем разница в стоимости. Кстати, точное значение этого двойника равно

0.73195874952002271118800535987247712910175323486328125

Теперь стоит отметить, что в арифметике с плавающей запятой могут происходить явно странные вещи, особенно когда оптимизации допускают 80-битную арифметику в одних ситуациях, но не в других и т. д.

Как говорит Хенк, если разница в последних двух битах вызывает у вас проблемы, значит, ваш дизайн неисправен.

person Jon Skeet    schedule 12.04.2012
comment
в арифметике с плавающей запятой могут происходить явно странные вещи, особенно когда оптимизация допускает 80-битную арифметику. Для получения дополнительной информации см. мой вопрос о согласованности чисел с плавающей запятой в C# - person BlueRaja - Danny Pflughoeft; 13.04.2012
comment
Нет, Math.pow отличается в C# и java. Обратите внимание на Double.doubleToLongBits(Math.pow(0.39215686274509803,1.0/3.0)) и BitConverter.DoubleToInt64Bits(Math.Pow(0.39215686274509803,1.0/3.0)). - person Stepan Yakovenko; 13.04.2012
comment
@ user643540: Да, если вы укажете буквальное значение непосредственно в вызове, вы можете получить другой ответ точно так же, как в моем предпоследнем абзаце. Но, как уже говорили все остальные, если одно отличие ULP вызывает у вас проблемы, вам следует позаботиться о своем дизайне. - person Jon Skeet; 13.04.2012

Если ваши расчеты чувствительны к такой разнице, вам потребуются другие меры (редизайн).

person Henk Holterman    schedule 12.04.2012
comment
Это не прямой ответ, нет. Но это проблема с проблемами XY. Ожидаете ли вы, что часть кода Java будет эмулировать pow C#? - person Henk Holterman; 13.04.2012

эта последняя цифра приводит к достаточным различиям в дальнейших расчетах

Это невозможно, потому что это одно и то же число. У double недостаточно точности, чтобы отличить 0.7319587495200227 от 0.73195874952002271; они оба представлены как

0.73195874952002271118800535987247712910175323486328125.

Разница заключается в округлении: в Java используется 16 значащих цифр, а в C# — 17. Но это всего лишь проблема с отображением.

person dan04    schedule 12.04.2012

И Java, и C# возвращают число с плавающей запятой IEEE (в частности, двойное число) из Math.Pow. Разница, которую вы видите, почти наверняка связана с форматированием, когда вы отображаете число как десятичное. Базовое (двоичное) значение, вероятно, то же самое, и ваши математические проблемы заключаются в другом.

person Chris Shain    schedule 12.04.2012
comment
Последние цифры легко могут отличаться. .net не гарантирует конкретных результатов и использует эту свободу. - person CodesInChaos; 13.04.2012
comment
Оба они возвращают двойное типизированное значение, однако, если не используется POW FPU (например, это функция FPU, а не какие-либо строгие настройки в языках), тогда используемый алгоритм может быть немного другим... было бы интересно иметь небольшой фрагмент, чтобы показать это в любом случае здесь. - person ; 13.04.2012

Арифметика с плавающей запятой по своей сути неточна. Вы утверждаете, что ответ С# «лучше», но ни один из них не точен. Например, Wolfram Alpha (который действительно намного точнее) дает следующие значения:

http://www.wolframalpha.com/input/?i=Pow%280.392156862745098%2C+1.0+%2F+3.0%29

Если разница единиц в 17-й цифре приводит к тому, что последующие вычисления идут неправильно, то я думаю, что есть проблема с вашей математикой, а не с реализацией Java pow. Вам нужно подумать о том, как реструктурировать ваши вычисления, чтобы они не зависели от таких незначительных различий.

person Ethan Brown    schedule 12.04.2012
comment
Бывают ситуации, когда вам не важны последние цифры фактических результатов, если они непротиворечивы и воспроизводимы. Но в этом случае вы не можете использовать встроенные в .net типы с плавающей запятой. - person CodesInChaos; 13.04.2012
comment
Я был исправлен в этом - математика с плавающей запятой очень точна. Проблема в том, что он работает с двоичными числами, поэтому может не быть точного конечного преобразования из десятичного числа в двоичное (аналогично, нет точного конечного представления 1/3 в десятичном, но в базисном виде). 3 это будет выражено как .1). Если ваши входные данные не могут быть точно выражены в двоичном формате, они будут округлены, и если вы отформатируете свой вывод как десятичный, он может быть снова округлен. - person Chris Shain; 13.04.2012
comment
Очень точно - субъективный термин. Это, вероятно, более точно, чем вам когда-либо понадобится, если только вы не работаете с сильно хаотичными системами. Я хочу сказать, что при ограниченном количестве места для хранения (64 бита) существует верхний предел точности, с которой вы можете выражать числа, независимо от основания. - person Ethan Brown; 13.04.2012

Точность в семнадцать цифр — это лучшее, что может сделать любое число с плавающей запятой IEEE, независимо от языка:

http://en.wikipedia.org/wiki/Double-precision_format_floating-point

person duffymo    schedule 12.04.2012