ошибка точности gcc?

У меня была та же проблема недавно. Так как моя служба была локальна, я закончил тем просто, что использовал статическое поле в классе обслуживания для переключения состояния, как описано hackbod здесь

РЕДАКТИРОВАНИЕ (для записи):

Вот решение, предложенное hackbod:

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

у Нас сознательно нет API, чтобы проверить, работает ли услуга, потому что, почти бесперебойно, когда Вы хотите сделать что-то как этот, что Вы заканчиваете с условиями состязания в своем коде.

8
задан Michael Petrotta 26 August 2009 в 23:17
поделиться

7 ответов

Меня это тоже укусило.

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

Но в В этом случае вы вычисляете t1 + t2 , а затем вычисляете его снова. Конечно, это должно дать идентичный результат?

Вот что, вероятно, происходит. Готов поспорить, вы используете это на процессоре x86, верно? FPU x86 использует 80 бит для своих внутренних регистров, но значения в памяти хранятся в виде 64-битных чисел двойной точности.

Итак, t1 + t2 сначала вычисляется с точностью 80 бит, затем - я полагаю - сохраняется в памяти в sum_2 с точностью 64 бита - и происходит некоторое округление. Для утверждения он загружается обратно в регистр с плавающей запятой, и t1 + t2 вычисляется снова, снова с точностью 80 бит. Итак, теперь вы сравниваете sum_2 , которое ранее было округлено до 64-битного значения с плавающей запятой, с t1 + t2 , которое было вычислено с более высокой точностью (80 бит) - и поэтому значения не совсем идентичны.

Edit Так почему же первый тест проходит? В этом случае компилятор, вероятно, оценивает 4.0 + 6.3 во время компиляции и сохраняет его как 64-битную величину - как для присваивания, так и для утверждения. Итак, идентичные значения сравниваются, и утверждение проходит.

Second Edit Вот ассемблерный код, сгенерированный для второй части кода (gcc, x86), с комментариями - в значительной степени соответствует сценарию, описанному выше:

// t1 = 4.0
fldl    LC3
fstpl   -16(%ebp)

// t2 = 6.3
fldl    LC4
fstpl   -24(%ebp)

// sum_2 =  t1+t2
fldl    -16(%ebp)
faddl   -24(%ebp)
fstpl   -32(%ebp)

// Compute t1+t2 again
fldl    -16(%ebp)
faddl   -24(%ebp)

// Load sum_2 from memory and compare
fldl    -32(%ebp)
fxch    %st(1)
fucompp

Интересное примечание: Это было скомпилировано без оптимизации. Когда он компилируется с -O3 , компилятор оптимизирует весь код.

12
ответ дан 5 December 2019 в 05:08
поделиться

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

void CompareFloats( double d1, double d2, double epsilon )
{
    assert( abs( d1 - d2 ) < epsilon );
} 

Это не имеет ничего общего с компилятором и все, что связано с тем, как числа с плавающей запятой реализовано. вот спецификация IEEE:

http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

13
ответ дан 5 December 2019 в 05:08
поделиться

Я продублировал вашу проблему на моем Intel Core 2 Duo и посмотрел ассемблерный код. Вот что происходит: когда ваш компилятор оценивает t1 + t2 , он выполняет

load t1 into an 80-bit register
load t2 into an 80-bit register
compute the 80-bit sum

Когда он сохраняет в sum_2 , он выполняет

round the 80-bit sum to a 64-bit number and store it

Затем сравнение == сравнивает 80-битную сумму с 64-битной суммой, и они разные, в первую очередь потому, что дробная часть 0.3 не может быть представлена ​​точно с использованием двоичного числа с плавающей запятой, поэтому вы сравниваете `` повторяющееся десятичное '' (фактически повторяющееся двоичное ), который был усечен до двух разной длины.

Что действительно раздражает, так это то, что если вы компилируете с gcc -O1 или gcc -O2 , gcc выполняет неправильные арифметические действия во время компиляции , и проблема исчезнет. Может это нормально по стандарту, но это еще одна причина, по которой gcc не мой любимый компилятор.


PS Когда я говорю, что == сравнивает 80-битную сумму с 64-битной суммой, я, конечно, действительно имею в виду, что он сравнивает расширенная версия 64-битной суммы. Возможно, вам стоит подумать, что

sum_2 == t1 + t2

преобразуется в

extend(sum_2) == extend(t1) + extend(t2)

, а

sum_2 = t1 + t2

преобразуется в

sum_2 = round(extend(t1) + extend(t2))

Добро пожаловать в чудесный мир с плавающей запятой!

3
ответ дан 5 December 2019 в 05:08
поделиться

Возможно, в одном из случаев вы в конечном итоге сравните 64-битный двойной с 80-битным внутренним регистром. Может быть полезно взглянуть на инструкции по сборке, которые GCC выпускает для двух случаев ...

2
ответ дан 5 December 2019 в 05:08
поделиться

Сравнение чисел двойной точности по своей сути неточно . Например, часто можно встретить 0,0 == 0,0 , возвращающее false . Это связано с тем, как FPU хранит и отслеживает числа.

Википедия говорит :

Проверка на равенство проблематична.

1
ответ дан 5 December 2019 в 05:08
поделиться

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

if (abs(x) != 0 || abs(y) != 0) 
   rel_diff (x, y) = abs((x - y) / max(abs(x),abs(y))
else
   rel_diff(x,y) = max(abs(x),abs(y))

Например,

rel_diff(1.12345, 1.12367)   = 0.000195787019
rel_diff(112345.0, 112367.0) = 0.000195787019
rel_diff(112345E100, 112367E100) = 0.000195787019

Идея состоит в том, чтобы измерить количество ведущих значащих цифр у чисел есть общее; если вы возьмете -log10 из 0,000195787019, вы получите 3,70821611, что примерно соответствует количеству ведущих десятичных цифр, общих для всех примеров.

Если вам нужно определить, равны ли два числа с плавающей запятой, вы должны сделать что-то вроде

if (rel_diff(x,y) < error_factor * machine_epsilon()) then
  print "equal\n";

, где машинный эпсилон - это наименьшее число, которое может храниться в мантиссе используемого оборудования с плавающей запятой. В большинстве компьютерных языков есть вызов функции для получения этого значения. error_factor должен основываться на количестве значащих цифр, которые, по вашему мнению, будут использованы при ошибках округления (и других) при вычислениях чисел x и y. Например, если бы я знал, что x и y были результатом примерно 1000 суммирований и не знал никаких границ суммируемых чисел, я бы установил error_factor примерно на 100.

Пытался добавить их как ссылки, но не смог поскольку это мой первый пост:

  • en.wikipedia.org/wiki/Relative_difference
  • en.wikipedia.org/wiki/Machine_epsilon
  • en.wikipedia.org/wiki/Significand (мантисса)
  • en .wikipedia.org / wiki / Rounding_error
3
ответ дан 5 December 2019 в 05:08
поделиться

Эту «проблему» можно «исправить» с помощью следующих параметров:

-msse2 -mfpmath = sse

, как описано на этой странице:

http: // www .network-theory.co.uk / docs / gccintro / gccintro_70.html

Как только я использовал эти параметры, оба утверждения прошли.

0
ответ дан 5 December 2019 в 05:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: