Какой правильный / стандартный способ проверить, меньше ли разница, чем точность станка?

36

Я часто оказываюсь в ситуациях, когда необходимо проверить, превышает ли полученная разница точность станка. Похоже , что для этой цели R имеет удобную переменную: .Machine$double.eps. Однако, когда я обращаюсь к исходному коду R за рекомендациями по использованию этого значения, я вижу несколько разных шаблонов.

Примеры

Вот несколько примеров из statsбиблиотеки:

t.test.R

if(stderr < 10 *.Machine$double.eps * abs(mx))

chisq.test.R

if(abs(sum(p)-1) > sqrt(.Machine$double.eps))

integrate.R

rel.tol < max(50*.Machine$double.eps, 0.5e-28)

lm.influence.R

e[abs(e) < 100 * .Machine$double.eps * median(abs(e))] <- 0

princomp.R

if (any(ev[neg] < - 9 * .Machine$double.eps * ev[1L]))

и т.п.

Вопросов

  1. Как можно понять рассуждение за все эти разные 10 *, 100 *, 50 *и sqrt()модификаторы?
  2. Существуют ли рекомендации по использованию .Machine$double.epsдля корректировки различий из-за проблем с точностью?
Каролис Концевичюс
источник
6
Таким образом, в обоих сообщениях делается вывод о том, что «разумная степень определенности» зависит от вашего заявления. В качестве примера вы можете проверить этот пост на R-devel ; «Ага! 100-кратная точность станка не так уж и велика, когда сами цифры двузначные». (Питер Далгаард, член команды R Core)
Хенрик
1
@ KarolisKoncevičius, я не думаю, что это так просто. Это связано с общими ошибками, присутствующими в математике с плавающей запятой, и с количеством операций, которые вы над ними выполняете. Если вы просто сравниваете числа с плавающей запятой, используйте double.eps. Если вы выполняете несколько операций над числом с плавающей запятой, то и ваша погрешность должна быть скорректирована. Вот почему all.equal дает вам toleranceаргумент.
Джозеф Вуд
1
Посмотрите также на Реализацию функциональности nextafter в R, что даст вам следующее большее двойное число.
GKi

Ответы:

4

Точность станка doubleзависит от его текущего значения. .Machine$double.epsдает точность, когда значения равны 1. Вы можете использовать функцию C, nextAfterчтобы получить точность станка для других значений.

library(Rcpp)
cppFunction("double getPrec(double x) {
  return nextafter(x, std::numeric_limits<double>::infinity()) - x;}")

(pr <- getPrec(1))
#[1] 2.220446e-16
1 + pr == 1
#[1] FALSE
1 + pr/2 == 1
#[1] TRUE
1 + (pr/2 + getPrec(pr/2)) == 1
#[1] FALSE
1 + pr/2 + pr/2 == 1
#[1] TRUE
pr/2 + pr/2 + 1 == 1
#[1] FALSE

Прибавление значения aк значению bне изменится, bесли оно aбудет <= вдвое меньше точности станка. Проверка, является ли разница меньше точности станка, сделана с <. Модификаторы могут учитывать типичные случаи, когда часто дополнение не показывало изменения.

В R точность станка можно оценить с помощью:

getPrecR <- function(x) {
  y <- log2(pmax(.Machine$double.xmin, abs(x)))
  ifelse(x < 0 & floor(y) == y, 2^(y-1), 2^floor(y)) * .Machine$double.eps
}
getPrecR(1)
#[1] 2.220446e-16

Каждое doubleзначение представляет диапазон. Для простого сложения диапазон результата зависит от диапазона каждого слагаемого, а также диапазона их суммы.

library(Rcpp)
cppFunction("std::vector<double> getRange(double x) {return std::vector<double>{
   (nextafter(x, -std::numeric_limits<double>::infinity()) - x)/2.
 , (nextafter(x, std::numeric_limits<double>::infinity()) - x)/2.};}")

x <- 2^54 - 2
getRange(x)
#[1] -1  1
y <- 4.1
getRange(y)
#[1] -4.440892e-16  4.440892e-16
z <- x + y
getRange(z)
#[1] -2  2
z - x - y #Should be 0
#[1] 1.9

2^54 - 2.9 + 4.1 - (2^54 + 5.9) #Should be -4.7
#[1] 0
2^54 - 2.9 == 2^54 - 2      #Gain 0.9
2^54 - 2 + 4.1 == 2^54 + 4  #Gain 1.9
2^54 + 5.9 == 2^54 + 4      #Gain 1.9

Для более высокой точности Rmpfrможно использовать.

library(Rmpfr)
mpfr("2", 1024L)^54 - 2.9 + 4.1 - (mpfr("2", 1024L)^54 + 5.9)
#[1] -4.700000000000000621724893790087662637233734130859375

В случае, если это может быть преобразовано в целое число gmpможет быть использовано (что находится в Rmpfr).

library(gmp)
as.bigz("2")^54 * 10 - 29 + 41 - (as.bigz("2")^54 * 10 + 59)
#[1] -47
ГКИ
источник
Большое спасибо. Я чувствую, что это гораздо лучший ответ. Это хорошо иллюстрирует многие моменты. Единственное, что мне все еще немного неясно - можно ли придумать модификаторы (например, * 9 и т. Д.) Самостоятельно? И если так, как ...
Каролис Концевичюс
Я думаю, что этот модификатор похож на уровень значимости в статистике и будет увеличиваться на количество операций, которые вы сделали в сочетании с выбранным риском, чтобы отклонить правильное сравнение.
GKi
3

Определение machine.eps: это самое низкое значение,  eps для которого  1+eps нет 1

Как правило (предполагая представление с плавающей запятой с основанием 2):
это epsсоставляет разницу для диапазона 1 .. 2,
для диапазона 2 .. 4 точность 2*eps
и т. Д.

К сожалению, здесь нет хорошего эмпирического правила. Это полностью определяется потребностями вашей программы.

В R мы имеем all.equal как встроенный способ проверки приближенного равенства. Таким образом, вы могли бы использовать что-то вроде (x<y) | all.equal(x,y)

i <- 0.1
 i <- i + 0.05
 i
if(isTRUE(all.equal(i, .15))) { #code was getting sloppy &went to multiple lines
    cat("i equals 0.15\n") 
} else {
    cat("i does not equal 0.15\n")
}
#i equals 0.15

В Google mock есть несколько средств сравнения с плавающей запятой для сравнений с двойной точностью, включая DoubleEqи DoubleNear. Вы можете использовать их в сопоставлении массива следующим образом:

ASSERT_THAT(vec, ElementsAre(DoubleEq(0.1), DoubleEq(0.2)));

Обновить:

Численные рецепты обеспечивают вывод, чтобы продемонстрировать, что использование одностороннего разностного коэффициента sqrtявляется хорошим выбором размера шага для конечно-разностных аппроксимаций производных.

Сайт статьи Википедии «Числовые рецепты», 3-е издание, раздел 5.7, страницы 229-230 (ограниченное количество просмотров страниц доступно по адресу http://www.nrbook.com/empanel/ ).

all.equal(target, current,
           tolerance = .Machine$double.eps ^ 0.5, scale = NULL,
           ..., check.attributes = TRUE)

Эта арифметика с плавающей точкой IEEE является хорошо известным ограничением компьютерной арифметики и обсуждается в нескольких местах:

, dplyr::near()еще один вариант проверки, если два вектора чисел с плавающей точкой равны.

Функция имеет встроенный параметр допуска: tol = .Machine$double.eps^0.5его можно настроить. Параметр по умолчанию совпадает с параметром по умолчанию для all.equal().

Срирам Наир
источник
2
Спасибо за ответ. На данный момент я думаю, что это слишком мало, чтобы быть принятым ответом. Кажется, он не затрагивает два основных вопроса из поста. Например, в нем говорится «это определяется потребностями вашей программы». Было бы неплохо показать один или два примера этого утверждения - может быть, небольшая программа и как с ее помощью можно определить толерантность. Может быть, с помощью одного из упомянутых скриптов R. Также all.equal()есть свое собственное допущение как допуск по умолчанию sqrt(double.eps)- почему это по умолчанию? Это хорошее правило для использования sqrt()?
Каролис Концевичюс
Вот код R, используемый для вычисления eps (извлеченный в свою собственную программу). Также я обновил Ответ многочисленными дискуссионными вопросами, которые я прошел ранее. Надеюсь, что то же самое поможет вам лучше понять.
Sreeram Nair
Искренний +1 за все усилия. Но в текущем состоянии я все еще не могу принять ответ. Кажется, это немного превосходит многие ссылки, но с точки зрения фактического ответа на 2 опубликованных вопроса: 1) как понять модификаторы 100x, 50x и т. Д. В stats::источнике R , и 2) каковы рекомендации; ответ довольно тонкий. Кажется, единственное применимое предложение - это ссылка из «Числовых рецептов» о том, что sqrt () является хорошим значением по умолчанию, что, на мой взгляд, действительно соответствует действительности. Или, может быть, я что-то здесь упускаю.
Каролис Концевичюс