Pi的Monte Carlo估算器


25

大家快乐的Pi日!完全没有理由,我试图构造一个尽可能短的Pi蒙特卡洛估计器。我们可以构建一个可以在推文中显示的推文吗?

为了澄清,我想到的是从单位平方中绘制随机点并计算落入单位圆内的比率的典型方法。样本数量可以硬编码也可以不硬编码。如果对它们进行硬编码,则必须至少使用1000个样本。结果可以作为浮点数,定点数或有理数返回或打印。

禁止使用Trig函数或Pi常数作为Monte Carlo方法。

这是代码高尔夫球,因此最短的提交(以字节为单位)获胜。


2
允许使用Trig函数吗?我建议您明确禁止它们。
级圣河

((0..4e9).map{rand**2+rand**2<1}.to_s.sub(/./,"$1.")
John Dvorak

@JanDvorak应该如何工作?难道不map给你一个trueand 数组false吗?
马丁·恩德

@MartinBüttner啊,抱歉。.filter{...}.size应该可以,但是。
约翰·德沃夏克

@JanDvorak确实。真的很棒:)
马丁·恩德

Answers:


17

80386机器代码,40 38字节

代码的十六进制转储:

60 33 db 51 0f c7 f0 f7 e0 52 0f c7 f0 f7 e0 58
03 d0 72 03 83 c3 04 e2 eb 53 db 04 24 58 db 04
24 58 de f9 61 c3

如何获取此代码(从汇编语言):

    // ecx = n (number of iterations)
    pushad;
    xor ebx, ebx; // counter
    push ecx; // save n for later
myloop:
    rdrand eax; // make a random number x (range 0...2^32)
    mul eax; // calculate x^2 / 2^32
    push edx;
    rdrand eax; // make another random number y
    mul eax; // calculate y^2 / 2^32
    pop eax;
    add edx, eax; // calculate D = x^2+y^2 / 2^32 (range 0...2^33)
    jc skip; // skip the following if outside the circle
    add ebx, 4; // accumulate the result multiplied by 4
skip:
    loop myloop;
    push ebx; // convert the result
    fild dword ptr [esp]; // to floating-point
    pop eax;
    fild dword ptr [esp]; // convert n to floating-point
    pop eax;
    fdivp st(1), st; // divide

    popad;
    ret;

这是一个使用MS fastcall调用约定的函数(迭代次数在register中传递ecx)。它将结果返回到st寄存器中。

关于此代码的有趣之处:

  • rdrand -仅3个字节即可生成一个随机数!
  • 它使用(无符号)整数算术运算直到最终除法。
  • 平方距离(D)与半径平方(2^32)的比较会自动执行-进位标志包含结果。
  • 要将计数乘以4,它将以4的步长对样本进行计数。

注释应显示为“计算x ^ 2%2 ^ 32”
Cole Johnson

@ColeJohnson否-随机数在其中eax;该mul命令本身乘以它,并把较高的部分放在里面edx; 低部分eax被丢弃。
anatolyg 2015年

11

Matlab /八度,27字节

我知道已经有一个Matlab / Octave答案,但是我尝试了自己的方法。我使用了4/(1+x^2)0和1之间的整数是pi 的事实。

mean(4./(1+rand(1,1e5).^2))

不同的算法总是很棒的!而且,效率更高!
anatolyg

7

R,40(或使用其他方法为28或24)

mean(4*replicate(1e5,sum(runif(2)^2)<1))

mean(4*sqrt(1-runif(1e7)^2))

mean(4/(1+runif(1e7)^2))

Python 2,56

如果允许numpy,则使用另一个Python,但与Matlab / Octave非常相似:

import numpy;sum(sum(numpy.random.rand(2,8e5)**2)<1)/2e5

6

Mathematica,42 40 39字节(或31/29?)

我有42个字节的所有三个解决方案:

4Count[1~RandomReal~{#,2},p_/;Norm@p<1]/#&
4Tr@Ceiling[1-Norm/@1~RandomReal~{#,2}]/#&
4Tr@Round[1.5-Norm/@1~RandomReal~{#,2}]/#&

它们都是未命名的函数,它们使用样本数,并且n返回有理近似值π。首先,它们都n在正象限的单位正方形中生成点。然后,他们确定位于单位圆内的那些样本的数量,然后除以样本数量,然后乘以4。唯一的区别在于它们如何确定单位圆内的样本数量:

  • 第一个使用Count条件为Norm[p] < 1
  • 第二个从中减去每个点的范数,1然后四舍五入。这会将单位圆内的数字变为,将单位圆1外的数字变为0。之后,我将它们全部加起来Tr
  • 第三个基本相同,但从中减去1.5,因此我可以使用Round代替Ceiling

Aaaaaand 撰写本文时,我想到确实存在一个较短的解决方案,如果我只是减去2然后使用Floor

4Tr@Floor[2-Norm/@1~RandomReal~{#,2}]/#&

或使用Unicode地板或天花板操作员保存另一个字节:

4Tr@⌊2-Norm/@1~RandomReal~{#,2}⌋/#&
4Tr@⌈1-Norm/@1~RandomReal~{#,2}⌉/#&

请注意,对于相同的字节,也可以使用Mean代替Tr和不使用编写三个基于舍入的解决方案/#


如果其他基于蒙特卡洛的方法很好(特别是彼得选择的方法),我可以通过使用的来估计或的整数来完成31个字节,这一次给出了一个浮点数:√(1-x2)1/(1+x2)

4Mean@Sqrt[1-1~RandomReal~#^2]&
Mean[4/(1+1~RandomReal~#^2)]&

9
您已经获得了关于生命,宇宙和万物的三种解决方案,而您决定毁了它?异端。
Seequ 2015年


6

CJam,27 23 22或20个字节

4rd__{{1dmr}2*mhi-}*//

Runner112节省2个字节,Sp3000节省1个字节

它以来自STDIN的迭代计数为输入。

这是直截了当的。这些是主要步骤:

  • 读取输入并运行多次Monte Carlo迭代
  • 在每次迭代中,获取两个从0到1的随机浮点数的平方和,然后查看它是否小于1
  • 求出总迭代次数小于1的次数的比率,然后乘以4得到PI

代码扩展

4rd                     "Put 4 on stack, read input and convert it to a double";
   __{            }*    "Take two copies, one of them determines the iteration"
                        "count for this code block";
      {1dmr}2*          "Generate 2 random doubles from 0 to 1 and put them on stack";
              mh        "Take hypot (sqrt(x^2 + y^2)) where x & y are the above two numbers";
                i       "Convert the hypot to 0 if its less than 1, 1 otherwise";
                 -      "Subtract it from the total sum of input (the first copy of input)";
                    //  "This is essentially taking the ratio of iterations where hypot";
                        "is less than 1 by total iterations and then multiplying by 4";

在这里在线尝试


如果将的平均值1/(1+x^2)也视为蒙特卡洛,则可以用20个字节来完成:

Urd:K{4Xdmr_*)/+}*K/

在这里尝试


2
我也尝试了一个CJam答案,并设法在您的分数之下输入2个字节。但是我的代码与您的代码非常相似,我觉得将其作为单独的答案发布很脏。一切都只是变量的选择,这两个优化相同的:得到0的随机数为1,1dmr而不是KmrK/,并检查平方之和大于1 i,而不是1>(我认为这一次是特别聪明) 。
Runer112

@ Runer112谢谢。该i诀窍是很整洁!而且该死的缺乏文档1dmr
Optimizer

5

Python 2 2,77 75字节

from random import*;r=random;a=0;exec"a+=r()**2+r()**2<1;"*4000;print a/1e3

使用4000个样本来保存字节 1e3


5
使用,您可以免费获得更高的准确性...*8000;print a/2e3
逻辑骑士

5

Commodore 64 Basic,45字节

1F┌I=1TO1E3:C=C-(R/(1)↑2+R/(1)↑2<1):N─:?C/250

PETSCII替代:= SHIFT+E/=SHIFT+N=SHIFT+O

在第一象限中产生1000点;对于每一个,将“ x ^ 2 + y ^ 2 <1”的真实性添加到运行计数中,然后将计数除以250得到pi。(存在负号是因为在C64上,“ true” = -1。)


怎么(1)办?
echristopherson

@echristopherson,您误读了它。 /不是分隔符号,而是SHIFT+N在Commodore 64键盘上打字产生的字符。 R/(1)是的快捷方式RND(1),即。“使用当前的RNG种子生成介于0和1之间的随机数”。
2015年

哦,对了!良好的旧PETSCII图形字符。
echristopherson

5

J,17个字节

计算范围内40000的函数样本值的平均值。4*sqrt(1-sqr(x))[0,1]

方便0 o.x退货sqrt(1-sqr(x))

   1e4%~+/0 o.?4e4$0
3.14915

4

> <>(鱼),114字节

:00[2>d1[   01v
1-:?!vr:@>x|  >r
c]~$~< |+!/$2*^.3
 .41~/?:-1r
|]:*!r$:*+! \
r+)*: *:*8 8/v?:-1
;n*4, $-{:~ /\r10.

现在,> <>没有内置的随机数生成器。但是,它确实具有沿随机方向发送指针的功能。我的代码中的随机数生成器:

______d1[   01v
1-:?!vr:@>x|  >r
_]~$~< |+!/$2*^__
 __________
___________ _
_____ ____ _______
_____ ____~ ______

它基本上会生成组成二进制数的随机位,然后将该随机二进制数转换为十进制。

其余只是平方法中的常规点。

用法:运行代码时,必须确保使用示例数量预先填充堆栈(在python解释器中为-v),例如

pi.fish -v 1000

退货

3.164

4

Matlab或Octave 29个字节(多亏了骗子!)

mean(sum(rand(2,4e6).^2)<1)*4

(我不太确定<1是否可以。我读到的应该是<= 1。但是精确得出1的概率有多大...)

Matlab或Octave 31字节

sum(sum(rand(2,4e3).^2)<=1)/1e3

1
很好的主意!您可以使用保存两个额外的字节mean(sum(rand(2,4e6).^2)<1)*4
瑕疵的

4

Java,108个字节

double π(){double π=0,x,i=0;for(;i++<4e5;)π+=(x=Math.random())*x+(x=Math.random())*x<1?1e-5:0;return π;}

四千次迭代,每当该点在单位圆内时加0.001。很基本的东西。

注意:是的,我知道通过更改π为单字节字符可以删除四个字节。我喜欢这样


为什么不进行9999次迭代?
Optimizer

1
@Optimizer它使总和更短。对于9999次迭代,我必须每次都添加一个更精确的数字,这将花费我一些数字。
Geobits 2015年

1
您可以使用数字“ 4e5”和“ 1e-5”来节省另一个字节并提高精度。
Vilmantas Baranauskas

@VilmantasBaranauskas谢谢!我总是忘记了:)用4e9和1e-9代替是很诱人的,但这要花相当长的时间……
Geobits 2015年

提示:打高尔夫球时,您实际上应该减少字节,而不是人为地增加字节
破坏的柠檬

3

Javascript:62个字节

for(r=Math.random,t=c=8e4;t--;c-=r()**2+r()**2|0);alert(c/2e4)

我使用了以前的(现在已删除)的javascript答案,并剃了5个字节。


您可以链接到cfern的答案以适当地给予好评
乔纳森·弗雷希

您的答案似乎是不允许的I / O的摘要。请修复或删除您的帖子。
Jonathan Frech '18

抱歉,我是新来的,我不知道该如何链接到以前显示的解决方案,现在看来已经删除了。关于代码段:我完全同意,但是那是以前的javascript解决方案的代码,我也认为这样做是无效的。我将我的程序修改为程序。
古斯曼·蒂尔诺

是; 先前的答案已被删除,因为它是无效的-我在建议删除之前已经看到了您的答案,因此删除了评论。+1用于提交有效答案;欢迎来到PPCG!
乔纳森·弗雷希

2

GolfScript(34个字符)

0{^3?^rand.*^.*+/+}2000:^*`1/('.'@

在线演示

这使用定点,因为GS实际上没有浮点。它稍微滥用了定点,因此,如果要更改迭代次数,请确保它是10的幂的两倍。

对于使用的特定蒙特卡洛方法,请归功于xnor


2

Python 2,90 85 81字节

from random import*;r=random;print sum(4.for i in[0]*9**7if r()**2+r()**2<1)/9**7

3.14120037157例如返回。样本计数为4782969(9 ^ 7)。使用9 ^ 9可以获得更好的pi,但您必须耐心等待。


由于您不使用,因此可以通过替换range(9**7)[0]*9**7或来节省3 i。而且该列表不太长,不会遇到内存问题。
Sp3000

谢谢。我想摆脱,range()但我完全忘记了这个把戏。
逻辑骑士

我觉得[0]9**7语法无效。
Seequ 2015年

你是对的。我重新连接了丢失的星号(它在我的桌子下面)。
逻辑骑士

2

Ruby,39个字节

p (1..8e5).count{rand**2+rand**2<1}/2e5

亮点之一是该8e5标记符能够使用表示法,这使得它可以在同一程序字节数内扩展到约8e9个样本。



1

Scala,87 77 66字节

def s=math.pow(math.random,2);Seq.fill(1000)(s+s).count(_<1)/250d

如果更换10008000250d2e4您既可以节省一个字节由8倍提高样本的数量
戴夫·斯沃茨

1

纯Bash,65个字节

for((;i++<$1*4;a+=RANDOM**2+RANDOM**2<32767**2));{ :;}
echo $a/$1

采用单个命令行参数,该参数乘以4即可得出样本数。Bash算法仅是整数,因此输出有理数。这可以通过管道传递bc -l给最终部门:

$ ./montepi.sh 10000
31477/10000
$ ./montepi.sh 10000|bc -l
3.13410000000000000000
$ 

1

20 19个字节

注意:这个答案是非竞争性的,因为添加了随机性的0​​.1.2版本是在挑战之后发布的。

命名函数F:

:%$,(4*/+1>/+*,?2~;

未命名函数:

%$,(4*/+1>/+*,?2~;)

这些都将样本计数作为参数并返回结果。它们如何工作?

%$,(4*/+1>/+*,?2~;)
   (4*/+1>/+*,?2~;) defines a chain, where functions are called right-to-left
               2~;  appends 2 to the argument, giving [x, 2]
              ?     create a table of random values from 0 to 1 with that shape
            *,      take square of every value
          /+         sum rows, giving a list of (x**2+y**2) values
        1>           check if a value is less than 1, per atom
      /+             sum the results
    4*               multiply by four
%$,                  divide the result by the original parameter

示例运行:

   :%$,(4*/+1>/+*,?2~;
   F400000
3.14154
   F400000
3.14302

1

dc,59个字符(空格被忽略)

[? 2^ ? 2^ + 1>i]su
[lx 1+ sx]si
[lu x lm 1+ d sm ln>z]sz

5k
?sn
lzx
lx ln / 4* p
q

我在Plan 9和OpenBSD上进行了测试,因此我想它可以在Linux(GNU?)上运行dc

逐行说明:

  1. 将代码存储到[读取并平方两个浮点;i如果寄存器中的1大于其平方和,则执行寄存器u
  2. 将代码存储到寄存器中x的[ 以1 为增量寄存器] i
  3. 将代码存储到寄存器中的[执行寄存器u,增量寄存器mz如果寄存器m大于寄存器,则执行寄存器n] z
  4. 将刻度设置为5个小数点。

  5. 从输入的第一行读取要采样的点数。
  6. 执行寄存器z
  7. 用寄存器除寄存器x(命中数)n(点数),结果乘以4,然后打印。
  8. 退出。

但是,我作弊:

该程序需要提供介于0和1之间的随机浮点数。

/* frand.c */
#include <u.h>
#include <libc.h>

void
main(void)
{
    srand(time(0));

    for(;;)
        print("%f\n", frand());
}

用法:

#!/bin/rc
# runpi <number of samples>

{ echo $1; frand } | dc pi.dc

测试运行:

% runpi 10000
3.14840

现在更少的作弊(100字节)

有人指出,我可以包括一个简单的prng。
http://en.wikipedia.org/wiki/RANDU

[lrx2^lrx2^+1>i]su[lx1+sx]si[luxlm1+dsmln>z]sz[0kls65539*2 31^%dsslkk2 31^/]sr?sn5dksk1sslzxlxlm/4*p

不打高尔夫球

[
Registers:
u - routine : execute i if sum of squares less than 1
i - routine : increment register x
z - routine : iterator - execute u while n > m++
r - routine : RANDU PRNG
m - variable: number of samples
x - variable: number of samples inside circle
s - variable: seed for r
k - variable: scale for division
n - variable: number of iterations (user input)
]c
[lrx 2^ lrx 2^ + 1>i]su
[lx 1+ sx]si
[lu x lm 1+ d sm ln>z]sz
[0k ls 65539 * 2 31^ % d ss lkk 2 31 ^ /]sr
? sn
5dksk
1 ss
lzx
lx lm / 4*
p

测试运行:

$ echo 10000 | dc pigolf.dc
3.13640

1

珀斯,19岁

c*4sm<sm^OQ2 2*QQQQ

给出所需的迭代次数作为输入。

示范

由于Pyth没有“随机浮点数”功能,因此我不得不即兴创作。程序选择两个小于输入,平方,和的随机正整数,然后与输入平方进行比较。这执行了等于输入的次数,然后将结果乘以4,然后除以输入。

在相关新闻中,我将很快向Pyth添加一个随机浮点数运算。但是,该程序不使用该功能。


如果我们解释为“结果可能会以浮点数,定点数或有理数返回或打印出来”。自由地,然后打印所得分数的分子和分母就足够了。在这种情况下:

珀斯,18岁

*4sm<sm^OQ2 2*QQQQ

这是一个相同的程序,只c删除了浮点除法运算()。


1

朱莉娅,37个字节

4mean(1-floor(sum(rand(4^8,2).^2,2)))

样本数为65536(= 4 ^ 8)。

更长的变体:以样本数s作为唯一参数的函数:

s->4mean(1-floor(sum(rand(s,2).^2,2)))

1

C,130字节

#include<stdlib.h>f(){double x,y,c=0;for(int i=0;i<8e6;++i)x=rand(),y=rand(),c+=x*x+y*y<1.0*RAND_MAX*RAND_MAX;printf("%f",c/2e6);}

取消高尔夫:

#include <stdlib.h>
f(){
 double x,y,c=0;
 for(int i=0; i<8e6; ++i) x=rand(), y=rand(), c+=x*x+y*y<1.0*RAND_MAX*RAND_MAX;
 printf("%f",c/2e6);
}

当然,您仍然应该发布不带空格的版本(将当前版本的标题保留为“ unolfed / with whitespace”或类似内容)
Destructible Lemon

@DestructibleWatermelon完成了!
Karl Napf

如果没有换行符,该解决方案就无法在GCC中工作f()。您使用什么编译器?见tio.run/##Pc49C4JAHIDx3U9xGMG9ZdYgwWkgtNbQ1BZ6L/UHO8M07hA/...
eush77


1

实际上,14个字节(非竞争)

`G²G²+1>`nkæ4*

在线尝试!

该解决方案是非竞争性的,因为该语言将挑战日期定为最新日期。样本数作为输入给出(而不是硬编码)。

说明:

`G²G²+1>`nkæ4*
`G²G²+1>`n      do the following N times:
 G²G²+            rand()**2 + rand()**2
      1>          is 1 greater?
          kæ    mean of results
            4*  multiply by 4

2
为什么要下票?
破坏的柠檬

1

拍子63字节

使用@Matt回答R语言的方法:

(/(for/sum((i n))(define a(/(random 11)10))(/ 4(+ 1(* a a))))n)

取消高尔夫:

(define(f n)
   (/
    (for/sum ((i n))
      (define a (/(random 11)10))
      (/ 4(+ 1(* a a))))
    n))

测试:

(f 10000)

输出(分数):

3 31491308966059784/243801776017028125

以十进制表示:

(exact->inexact(f 10000))

3.13583200307849

1

Fortran(GFortran)84 83字节

CALL SRAND(0)
DO I=1,4E3
X=RAND()
Y=RAND()
IF(X*X+Y*Y<1)A=A+1E-3
ENDDO
PRINT*,A
END

在线尝试!

这段代码写得很不好。如果gfortran决定初始化变量,它将失败A使用其他值(而不是0)(大约占编译的50%),,并且如果A将其初始化为0,它将始终为给定种子生成相同的随机序列。然后,始终打印相同的Pi值。

这是一个更好的程序:

Fortran(GFortran)100 99字节

A=0
DO I=1,4E3
CALL RANDOM_NUMBER(X)
CALL RANDOM_NUMBER(Y)
IF(X*X+Y*Y<1)A=A+1E-3
ENDDO
PRINT*,A
END

在线尝试!

(每个版本中节省了一个字节;感谢Penguino)。


1
在每个版本中,您可以通过将'DO I = 1,1E3'更改为'DO I = 1,4E3',将'A = A + 1'更改为'A = A + 1E-3'并更改' PRINT *,A / 250'到'PRINT *,A'
Penguino

是的,您确定!谢谢你的建议!
rafa11111 '18

1

Japt,26或18个字节

o r_+ÂMhMr p +Mr p <1Ã*4/U

在线尝试!

Optimizer的答案类似,主要是尝试学习Japt。
将要运行的迭代次数作为隐式输入U

o                           Take the input and turn it into a range [0, U),
                            essentially a cheap way to get a large array.
  r_                        Reduce it with the default initial value of 0.
    +Â                      On each iteration, add one if
      MhMr p +Mr p          the hypotenuse of a random [0,1)x[0,1) right triangle
                   <1       is smaller than one.
                     Ã*4/U  Multiply the whole result by four and divide by input.

如果1/(1+x^2)允许(而不是两个单独的随机数),那么我们可以使用相同的逻辑获得18个字节。

o r_Ä/(1+Mr pÃ*4/U

1
您可以通过Mh计算斜边而不是自己计算来节省一些字节;-)另外,您可以使用x取数组的总和,而不用加法来减少:o x@MhMr Mr)<1Ã*4/U
ETHproductions

@ETHproductions整洁,我不知道您可以那样使用Mh,谢谢!您的两个随机答案几乎和我的答案一样短,只有一个随机答案,这很酷。我会x记住,在尝试打高尔夫球时,我倾向于使用很多减少功能,因此这非常方便。
Nit

1

F#,149个字节

open System;
let r=new Random()
let q()=
 let b=r.NextDouble()
 b*b
let m(s:float)=(s-Seq.sumBy(fun x->q()+q()|>Math.Sqrt|>Math.Floor)[1.0..s])*4.0/s

在线尝试!

据我所知,要在F#中进行这种总计,创建一个数字数组并使用该Seq.sumBy方法比使用afor..to..do块。

此代码的作用是创建一个从1到1的浮点数的集合s,执行fun x->...该集合中元素数量的功能,并对结果求和。s集合中有元素,因此随机测试要进行s多次。集合中的实际数字将被忽略(fun x->,但x不使用)。

这也意味着应用程序必须首先创建并填充数组,然后对其进行迭代。因此它的速度可能是for..to..do循环的两倍。通过数组创建,内存使用量在O(f ** k)范围内!

对于实际测试本身,if then else它不用计算语句,而是计算距离(q()+q()|>Math.Sqrt)并四舍五入为Math.Floor。如果距离在圆内,则将四舍五入为0。如果距离在圆外,则将四舍五入为1。Seq.sumBy然后,方法将总计这些结果。

请注意,Seq.sumBy总计的不是圆的点,而是圆的点。因此,结果需要s(我们的样本量)并从中减去总数。

看起来,将样本大小作为参数比对值进行硬编码要短。所以我有点作弊...


1

Haskell中,116个 114 110 96字节

d=8^9
g[a,b]=sum[4|a*a+b*b<d*d]
p n=(sum.take(floor n)$g<$>iterate((\x->mod(9*x+1)d)<$>)[0,6])/n

因为处理import System.Random; r=randoms(mkStdGen 2)会占用太多宝贵的字节,所以我用线性同余生成器生成了一个无限数量的随机数列表,有人说这几乎在密码学上很强:x↦x*9+1 mod 8^9,根据Hull-Dobell定理,整个周期为8^9

g产率4,如果该随机数点是圆为对随机数的内部中[0..8^9-1],因为这消除了式中乘法使用。

用法:

> p 100000
3.14208

在线尝试!


1

Perl 5,34个字节

$_=$a/map$a+=4/(1+(rand)**2),1..$_

样本数取自stdin。需要-p

起作用是因为:

在线尝试!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.