2015-10-20 4 views
1

Я пишу код на разных языках, который включает арифметику с двойной точностью. В идеале программы должны давать точно такие же значения. Я знаю, что не все двойные/float-арифметики детерминированы (объясняется здесь красиво: https://randomascii.wordpress.com/2013/07/16/floating-point-determinism/), поэтому мне нужно быть осторожным. Может кто-нибудь объяснить, что здесь происходит, хотя ?:Нечеткое совпадение между C и Java

программы C:

#include <stdio.h> 
#include <stdlib.h> 

int 
main(void) { 
    printf("%.52f\n", 1.66007664274403694e-03); 
    return (EXIT_SUCCESS); 
} 

Результат: 0.0016600766427440369430584832244335302675608545541763

"Эквивалент" Java 8 программ:

class A { 
    public static void main(String args[]) { 
     System.out.printf("%.52f\n", 1.66007664274403694e-03D); 
    } 
} 

Результат: 0.0016600766427440370000000000000000000000000000000000

Результаты разные. У меня такое ощущение, что это может быть связано с режимами округления с плавающей запятой, однако, насколько я вижу, C и Java имеют одинаковые значения по умолчанию (?).

Как я могу обеспечить, чтобы обе программы имели одинаковый результат?

EDIT:

FWIW, если я печатаю константа как BigDecimal: System.out.printf("%.52f\n", new BigDecimal(1.66007664274403694e-03));, я получаю: 0.0016600766427440369430584832244335302675608545541763. Это может доказать, что это не проблема отображения, но кто знает, какая магия JVM делает под ним.

EDIT2:

Использование strictfp, как @ Крис-к предполагает, я аннотированный класс, а результат остается 0.0016600766427440370000000000000000000000000000000000.

EDIT3:

Другое предложение попробовать System.out.printf("%.52f\n", new BigDecimal("1.66007664274403694e-03"));, что дает результат, который мы еще не видели: 0.0016600766427440369400000000000000000000000000000000.

+0

Число округлено на 18-м месте после десятичного знака, а не на большом вопросе. – ameyCU

+1

вы можете использовать 'BigDecimal' для более точных вычислений в java – Andrew

+0

Да, я мог бы использовать BigDecimal за счет эффективности, но поскольку стандартизированы парные номера, как получается несоответствие? –

ответ

2

Я думаю, что это проблема с дисплеем. Спецификатор точности в System.out.printf не позволяет повысить точность за определенный период (после этого он просто печатает 0 цифр).

Если вы не заботитесь столько о печатных и просто хотите, чтобы это было то же самым между ними, извлечь знак/мантиссу/экспоненту и печатать их, например, в Java:

long mantissa = Double.doubleToLongBits(f) & 0x000fffffffffffffL; 
    long exponent = Double.doubleToLongBits(f) & 0x7ff0000000000000L; 
    long sign = Double.doubleToLongBits(f) & 0x8000000000000000L; 

    // Exponent is encoded with a bias, so to see the actual exponent 
    // value: 
    exponent >>= (13 * 4); 
    exponent -= 1023; 

    // Leading 1 bit for mantissa isn't stored: 
    mantissa |= 0x0010000000000000L; 

    if (sign != 0) sign = 1; 
    System.out.println("" + sign + "/" + mantissa + "/" + exponent); 

Для Java это принты:

0/7655752242860553/-10 

Эквивалент код C (работает с GCC на x86-64):

#include <string.h> 
#include <stdio.h> 

int main(int argc, char ** argv) 
{ 
    double f = 1.66007664274403694e-03; 

    long long doubleBits; 
    memcpy(&doubleBits, &f, sizeof(long long)); 

    long long mantissa = doubleBits & 0x000fffffffffffffLL; 
    long long exponent = doubleBits & 0x7ff0000000000000LL; 
    long long sign = doubleBits & 0x8000000000000000LL; 

    // Exponent is encoded with a bias, so to see the actual exponent 
    // value: 
    exponent >>= (13 * 4); 
    exponent -= 1023; 

    // Leading 1 bit for mantissa isn't stored: 
    mantissa |= 0x0010000000000000L; 

    if (sign != 0) sign = 1; 
    printf("%lld/%lld/%lld\n", sign, mantissa, exponent); 

    return 0; 
} 

на моя машина, он производит тот же вывод. Это показывает, что внутреннее представление константы на обеих платформах одинаково (конечно, это может быть не универсально верно для архитектур и реализаций). Расхождение в результатах ваших исходных программ объясняется различиями в реализации функций printf. (Версия Java, по-видимому, перестает вычислять цифры после некоторой точки и вместо этого печатает 0, возможно, чтобы избежать печати констант с плавающей запятой по-разному, как они написаны в исходном коде).

С другой стороны, если бы это было рассчитывается значение, а не константа вы, вероятно, иметь более трудное время получить значение между C и Java, чтобы соответствовать :)

+0

* возможно * мое редактирование доказывает, что это не проблема с отображением (?) –

+0

@EddBarrett нет, это не так. Вы до сих пор пережили двойной, прежде чем перейти в BigDecimal. Измените аргумент arg в конструкторе на строку, чтобы предотвратить двойное изменение значения. –

+0

Тогда результатом будет '0,00166007664274403694000000000000000000000000000000000000', то есть новый вариант. –

0

Используйте BigDecimal в Java для таких операций:

BigDecimal x = new BigDecimal(1.66007664274403694e-03D); 
    System.out.printf(x.toPlainString()); 

Примитивный поплавок в Java всего 4 байта в ширину.

+0

Даже если суффикс с D? –

+0

Если вы используете d как суффикс, это двойной. – mwe

2

Вы могли бы очень хорошо иметь дисплей проблема здесь только. Однако во время вычислений Java свободно отличается от поведения C.

Java может использовать дополнительную точность в вычислении с плавающей точкой, если она доступна на платформе. Однако Java предлагает переопределить, объявить свой двойной как strictfp, и это будет информировать JVM о том, чтобы ограничить вычисления, сделав их более переносимыми.

Вы также можете проверить класс StrictMath на Java, он также имеет несколько utils, которые были реализованы в C для обеспечения совместимости.

+0

Я отредактировал вопрос, добавив результат вашего предложения. –

+0

Jave не допускается, в общем, использовать дополнительную точность, только более широкий диапазон экспонентов. См. JLS: [15.4. FP-строгие выражения] (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.4). C, с другой стороны, делает дополнительную точность. –

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