2015-09-24 6 views
-3

Первая проблемаC/C++ Арифметическое выражение с двойными числами - странные результаты - перевод на Яве

В коде C у меня есть это выражение:

double completeExpression = x1 - h*exp(-lambda*t); 

я разделить его на две операции:

double value = h*exp(-lambda*t); 
double subtraction = x1 - value; 

Проблема в том, что вычитание отличается от полного выражения. Что случилось?

Я воспроизвел странные результаты в моем коде с этой строки:

const double TOLERANCE = 1e-16; 
double h = 0.51152525298500628; 
double lambda =0.99999999999999978; 
double t=0.1; 
double x1 =0.4628471891711442 ; 

double completeExpression = x1 - h*exp(-lambda*t); 
double value = h*exp(-lambda*t); 
double subtraction = x1 - value; 

printf("x1 = %1.4e & value = %1.4e",x1,value); 
printf("\ncompleteExpression = %1.4e",completeExpression); 
printf("\nsubtraction = %1.4e",subtraction); 

Результаты:

x1 = 4.6285e-001 & value = 4.6285e-001 
completeExpression = 8.2779e-017 
subtraction = 5.5511e-017 

Вторая проблема:

я должен перевести completeExpression в Java, и я всегда возвращал результат плохого результата (вычитания) и не полныйExpression:

Код:

static double TOLERANCE = 1e-16; 
    public static void main() { 

     double h = 0.51152525298500628; 
     double lambda =0.99999999999999978; 
     double t=0.1; 
     double x1 =0.4628471891711442 ; 

     double completeExpression = x1 - h*Math.exp(-lambda*t); 
     double value = h*Math.exp(-lambda*t); 
     double subtraction = x1 - value; 

     System.out.println("x1 = " + String.format("%1.4e", value) + "& value = " + String.format("%1.4e",x1)); 
     System.out.println("\ncompleteExpression = " + String.format("%1.4e",completeExpression)); 
     System.out.println("\nsubtraction = " + String.format("%1.4e",subtraction)); 

#gcc --version 

My Gcc Version: 
$ gcc --version 
gcc.exe (GCC) 4.8.1 
Copyright (C) 2013 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+0

Проверьте это: http://stackoverflow.com/questions/31418209/double-multiplication-differs-between-compile-time-and-runtime-in-32-bit-platfor – ouah

+0

Похож на очень распространенную вычислительную маржу, когда работа с нецелыми – YePhIcK

+1

Расчеты с плавающей точкой неточны; следовательно, полагаясь на точные результаты (например, тестирование двух чисел является неравномерным) является плохой идеей. Если вы увеличиваете точность в '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' – Hurkyl

ответ

1

чисел с плавающей точкой (в отличие от целых чисел) почти никогда не точно то же самое. Причина в том, как они хранятся (с мантиссой и экспонентом).

В конце концов вы никогда не можете быть уверены, что два числа с плавающей запятой одинаковы после выполнения «одинаковых» операций над ними. И более того - if(subtraction!=completeExpression) обычно недействителен. Вместо этого вы должны искать «близко матч»:

if(abs(subtraction - completeExpression) < TOLERANCE) 

где TOLERANCE некоторые постоянные у вас есть, как const double TOLERANCE = 1e-16;

Для получения дополнительной информации о том, почему этом числе с плавающей точкой «приблизительным» вы можете прочитать Wiki на Floating point. Но основная причина заключается в том, что диапазон чисел, представленных значениями с плавающей запятой, намного больше, чем число цифр, которые могут быть закодированы в заданное пространство.

32-разрядное целое число может кодировать значения от -2 до +2 ГБ, но диапазон 32-битных чисел с плавающей точкой лежит от -3.4e38 до + 3.4e38. Это разница в диапазоне более 20 цифр!

Для 64-разрядных значений разница в диапазоне еще больше и составляет почти 300 цифр.

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

Вообще говоря два числа 123e456 и 1.23e458 (при представлении с плавающей запятой IEEE 754 двоичного формата) по-прежнему будет отличаться, даже если математически они абсолютно равны.

+0

Проблема заключается не в количестве десятичных знаков, которые могут быть связаны с комбинацией мантиссы и экспоненты, но число, которое может быть представлено только мантиссой. Номера в исходном сообщении OP (кроме 0.1) выглядят так, как будто они неточно представлены. – marko

+0

Я заново напишу свой ответ, чтобы сделать этот пункт более ясным (я согласен - мантисса - это то, что ограничивает точность) – YePhIcK

+0

@YePhlcK спасибо за предложение, я отредактировал код. К сожалению, проблема всегда верна – michele