Подтвердить что ты не робот

В R почему факториал (100) отображается по-разному с prod (1:100)?

В R я нахожу какое-то странное поведение, которое я не могу объяснить, и я надеюсь, что кто-то здесь сможет. Я считаю, что значение 100! это большой номер.

Несколько строк из консоли показывают ожидаемое поведение...

>factorial( 10 )
[1] 3628800
>prod( 1:10 )
[1] 3628800
> prod( as.double(1:10) )
[1] 3628800
> cumprod( 1:10 )
[1]       1       2       6      24     120     720    5040   40320  362880 3628800

Однако, когда я пробую 100! Я получаю (заметьте, как результирующие числа начинают различаться в 14 цифр):

> options(scipen=200) #set so the whole number shows in the output
> factorial(100)
[1] 93326215443942248650123855988187884417589065162466533279019703073787172439798159584162769794613566466294295348586598751018383869128892469242002299597101203456
> prod(1:100)
[1] 93326215443944102188325606108575267240944254854960571509166910400407995064242937148632694030450512898042989296944474898258737204311236641477561877016501813248
> prod( as.double(1:100) )
[1] 93326215443944150965646704795953882578400970373184098831012889540582227238570431295066113089288327277825849664006524270554535976289719382852181865895959724032
> all.equal( prod(1:100) , factorial(100) , prod( as.double(1:100) ) )
[1] TRUE

Если я сделаю некоторые тесты против переменной, установленной в "известное" число 100! то я вижу следующее:

# This is (as far as I know) the 'true' value of 100!
> n<- as.double(93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000)
> factorial(100) - n
[1] -1902315522848807765998160811905210717565551993186466795054798772271710903343294674760811531554315419925519536152107160826913610179566298858520576
> prod(1:100) - n
[1] -48777321098687378615337456715518223527321845979140174232174327494146433419058837814379782860367062049372295798771978482741374619988879457910784
> prod(as.double(1:100)) - n
[1] 0

Конечный результат оценивается в ноль, но число, возвращаемое для prod( as.double( 1:100 ) ), не отображается так, как я ожидал бы, даже если он правильно оценивает prod( as.double( 1:100 ) ) - n, где n - это переменная, установленная на значение 100!.

Может ли кто-нибудь объяснить это поведение мне, пожалуйста? Насколько я знаю, это не должно быть связано с переполнением и т.д., Поскольку я использую систему x64. Версия и информация о машине ниже:

> .Machine$double.xmax
[1] 1.798e+308
> str( R.Version() )
List of 14
 $ platform      : chr "x86_64-apple-darwin9.8.0"
 $ arch          : chr "x86_64"
 $ os            : chr "darwin9.8.0"
 $ system        : chr "x86_64, darwin9.8.0"
 $ status        : chr ""
 $ major         : chr "2"
 $ minor         : chr "15.2"
 $ year          : chr "2012"
 $ month         : chr "10"
 $ day           : chr "26"
 $ svn rev       : chr "61015"
 $ language      : chr "R"
 $ version.string: chr "R version 2.15.2 (2012-10-26)"
 $ nickname      : chr "Trick or Treat"

Может кто-нибудь объяснить это мне? Я не сомневаюсь, что R делает все правильно, и это, скорее всего, связано с R. Вы можете указать, что поскольку prod( as.double( 1:100 ) ) - n правильно оценивает, о чем я беспокоюсь, но я делаю Project Euler Проблема 20, поэтому мне нужно было отобразить правильные цифры.

Спасибо

4b9b3361

Ответ 1

Ваш тест с помощью all.equal не дает ожидаемого результата. all.equal может сравнивать только два. Третий аргумент позиционируется на tolerance, что дает допуск операции сравнения. В вашем вызове all.equal вы даете ему допуск 100!, который определенно приводит к тому, что сравнение истинно для абсурдно разных значений:

> all.equal( 0, 1000000000, prod(as.double(1:100)) )
[1] TRUE

Но даже если вы укажете только два аргумента, например

all.equal( prod(1:100), factorial(100) )

он все равно произведет TRUE, потому что допустимое значение по умолчанию .Machine$double.eps ^ 0.5, например. два операнда должны соответствовать примерно 8 цифрам, что определенно имеет место. С другой стороны, если вы установите допуск 0, то ни одна из трех возможных комбинаций не станет равной сравнению:

> all.equal( prod(1:100), factorial(100), tolerance=0.0 )
[1] "Mean relative difference: 1.986085e-14"
> all.equal( prod(1:100), prod( as.double(1:100) ), tolerance=0.0 )
[1] "Mean relative difference: 5.22654e-16"
> all.equal( prod(as.double(1:100)), factorial(100), tolerance=0.0 )
[1] "Mean relative difference: 2.038351e-14"

Также обратите внимание, что только потому, что вы сказали R напечатать 200 значительных чисел, это не значит, что они все правильные. Действительно, 1/2 ^ 53 имеет около 53 десятичных цифр, но только первые 16 считаются значимыми.

Это также делает ваше сравнение с истинным значением ошибочным. Соблюдайте это. Конечными цифрами в том, что R дает вам для factorial(100), являются:

...01203456

Вы вычитаете из него n, где n - это "истинное" значение 100! поэтому он должен иметь 24 нуля в конце и, следовательно, разница также должна заканчиваться теми же цифрами, что и factorial(100). Но скорее это заканчивается:

...58520576

Это только показывает, что все эти цифры несущественны, и не следует действительно смотреть на их значение.

Требуется 525 бит бинарной точности, чтобы точно представлять 100! - что 10x точность double.

Ответ 2

Это должно быть сделано не с максимальным значением для double, а с его точностью.

100! имеет 158 значащих (десятичных) цифр. IEEE double (64 бит) имеет 52 бит пространства для хранения мантиссы, поэтому вы получаете ошибки округления после превышения примерно 16 десятичных цифр точности.

Кстати, 100! на самом деле, как вы подозревали,

93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

поэтому все вычисленные значения R неверны.

Теперь я не знаю R, но кажется, что all.equal() преобразует все три этих значения в float перед сравнением, и поэтому их отличия теряются.

Ответ 3

Я добавлю третий ответ, чтобы графически описать поведение, с которым вы сталкиваетесь. По сути, двойная точность для факториального расчета достаточна до 22!, затем она начинает все больше расходиться с реальным значением.

Вокруг 50! существует еще одно различие между двумя методами factorial (x) и prod (1: x), причем последнее дает, как вы указали, значения, более похожие на "реальный" фактор.

Factorial calculation precision in R

Код прилагается:

# Precision of factorial calculation (very important for the Fisher Exact Test)
library(gmp)
perfectprecision<-list()
singleprecision<-c()
doubleprecision<-c()
for (x in 1:100){
    perfectprecision[x][[1]]<-factorialZ(x)
    singleprecision<-c(singleprecision,factorial(x))
    doubleprecision<-c(doubleprecision,prod(1:x))
}


plot(0,col="white",xlim=c(1,100),ylim=c(0,log10(abs(doubleprecision[100]-singleprecision[100])+1)),
        ,ylab="Log10 Absolute Difference from Big Integer",xlab="x!")
for(x in 1:100) {
    points(x,log10(abs(perfectprecision[x][[1]]-singleprecision[x])+1),pch=16,col="blue")
    points(x,log10(abs(perfectprecision[x][[1]]-doubleprecision[x])+1),pch=20,col="red")
}
legend("topleft",col=c("blue","red"),legend=c("factorial(x)","prod(1:x)"),pch=c(16,20))

Ответ 4

Ну, вы можете сказать из тела factorial, что он вызывает gamma, который вызывает .Primitive("gamma"). Что выглядит .Primitive("gamma")? Как это.

Для больших входов поведение .Primitive("gamma") находится на строке 198 этого кода. Он вызывает

exp((y - 0.5) * log(y) - y + M_LN_SQRT_2PI +
            ((2*y == (int)2*y)? stirlerr(y) : lgammacor(y)));

который приближенно.


Кстати, статья в Rmpfr использует factorial в качестве примера. Поэтому, если вы пытаетесь решить проблему, просто используйте библиотеку Rmpfr.