В 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, поэтому мне нужно было отобразить правильные цифры.
Спасибо
Ответы
Ответ 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
.