2个给定数字之间的分数密度

Per*_*dor 5 c++ algorithm math fractions

我正在尝试对一个简单的Fraction类做一些分析,我想要一些数据来比较那个类型doubles.

问题

对,知道我正在寻找一些好的方法来获得2个数字之间的分数密度.馏分是基本上2点的整数(例如pair< long, long>),以及之间的密度st在该范围内可表示数的量.并且它需要是在O(1)或非常快的情况下完成的精确或非常好的近似.

为了使它更简单,假设我想要s和t之间的所有数字(不是分数)a/b,其中0 <= s <= a/b <t <= M,0 <= a,b < = M(b> 0,a和b是整数)

如果我的分数是一个只计数为6(M = 6)的数据类型,并且我希望密度在0和1之间,那么答案将是12.这些数字是:

0, 1/6, 1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 5/6.
Run Code Online (Sandbox Code Playgroud)

我已经想到了什么

一种非常天真的方法是循环所有可能的分数,并计算那些不能简化的分数.就像是:

long fractionsIn(double s, double t){
    long density = 0;
    long M = LONG_MAX;
    for(int d = 1; d < floor(M/t); d++){
        for(int n = ceil(d*s); n < M; n++){
            if( gcd(n,d) == 1 )
                density++;
        }
    }
    return density;
}
Run Code Online (Sandbox Code Playgroud)

但是gcd()很慢,所以它不起作用.我也尝试做一些数学但我无法做任何好事.

感谢@ m69的答案,我为此制作了以下代码Fraction = pair<Long,Long>:

//this should give the density of fractions between first and last, or less.
double fractionsIn(unsigned long long first, unsigned long long last){
    double pi = 3.141592653589793238462643383279502884;
    double max = LONG_MAX;  //i can't use LONG_MAX directly
    double zeroToOne = max/pi * max/pi * 3; // = approx. amount of numbers in Farey's secuence of order LONG_MAX. 
    double res = 0;

    if(first == 0){
        res = zeroToOne;
        first++;
    }

    for(double i = first; i < last; i++){
        res += zeroToOne/(i * i+1);
        if(i == i+1)
            i = nextafter(i+1, last);   //if this happens, i might not count some fractions, but i have no other choice
    }

    return floor(res);
}
Run Code Online (Sandbox Code Playgroud)

主要变化是nextafter,这对于大数字很重要(1e17)

结果

正如我在开始时解释,我是想比较Fractionsdouble.这是结果Fraction = pair<Long,Long>(这里我得到了双打密度):

Density between 0,1:                | 1,2              | 1e6,1e6+1   | 1e14,1e14+1 | 1e15-1,1e15 | 1e17-10,1e17 | 1e19-10000,1e19 | 1e19-1000,1e19
Doubles:        4607182418800017408 | 4503599627370496 | 8589934592  | 64          | 8           | 1            | 5               | 0
Fraction:       2.58584e+37         | 1.29292e+37      | 2.58584e+25 | 2.58584e+09 | 2.58584e+07 | 2585         | 1               | 0
Run Code Online (Sandbox Code Playgroud)

m69*_*g'' 7

密度介于0和1之间

如果表示分数的整数在0~M范围内,那么值0(包括)和1(不包括)之间的分数密度为:

M:      1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  
0~(1):  1   2   4   6  10  12  18  22  28  32  42  46  58  64  72  80  96 102 120 128 140 150 172 180 200 212 230 242 270 278 308 ...  
Run Code Online (Sandbox Code Playgroud)

这是OEIS上的序列A002088.如果向下滚动到公式部分,您将找到有关如何近似的信息,例如:

Φ(Ñ)=(3÷π 2Ñ 2 + O [ Ñ ×(LN Ñ)2/3 ×(LN LN Ñ)4/3 ]

(不幸的是,没有更详细地介绍O [x]部分中涉及的常数.请参阅下面关于近似质量的讨论.)

跨范围分布

从0到1的间隔包含可以用最多M的数字表示的唯一分数总数的一半; 例如,这是M = 15时的分布(即4位整数):

0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  
 72  36  12   6   4   2   2   2   1   1   1   1   1   1   1   1  
Run Code Online (Sandbox Code Playgroud)

总共144个独特的分数.如果查看M的不同值的序列,您将看到此序列中的步骤收敛:

    0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  
 1:   1   1
 2:   2   1   1
 3:   4   2   1   1
 4:   6   3   1   1   1
 5:  10   5   2   1   1   1
 6:  12   6   2   1   1   1   1
 7:  18   9   3   2   1   1   1   1
 8:  22  11   4   2   1   1   1   1   1
 9:  28  14   5   2   2   1   1   1   1   1
10:  32  16   5   3   2   1   1   1   1   1   1
11:  42  21   7   4   2   2   1   1   1   1   1   1
12:  46  23   8   4   2   2   1   1   1   1   1   1   1
13:  58  29  10   5   3   2   2   1   1   1   1   1   1   1
14:  64  32  11   5   4   2   2   1   1   1   1   1   1   1   1
15:  72  36  12   6   4   2   2   2   1   1   1   1   1   1   1   1
Run Code Online (Sandbox Code Playgroud)

密度不仅在分数总数的0到1的一半之间,而且1到2之间的密度是四分之一,2到3之间的密度接近第十二,依此类推.

随着M值的增加,0-1,1-2,2-3范围内的分数分布收敛于:

1/2, 1/4, 1/12, 1/24, 1/40, 1/60, 1/84, 1/112, 1/144, 1/180, 1/220, 1/264 ...  
Run Code Online (Sandbox Code Playgroud)

这个序列可以通过1/2开始计算,然后:

0-1:    1/2 x 1/1 =   1/2
1-2:    1/2 x 1/2 =   1/4  
2-3:    1/4 x 1/3 =  1/12  
3-4:   1/12 x 2/4 =  1/24  
4-5:   1/24 x 3/5 =  1/40  
5-6:   1/40 x 4/6 =  1/60  
6-7:   1/60 x 5/7 =  1/84  
7-8:   1/84 x 6/8 = 1/112  
8-9:  1/112 x 7/9 = 1/144 ...  
Run Code Online (Sandbox Code Playgroud)

您当然可以直接计算这些值中的任何一个,而无需中间的步骤:

0-1: 1/2  
6-7: 1/2 x 1/6 x 1/7 = 1/84  
Run Code Online (Sandbox Code Playgroud)

(另请注意,分布序列的后半部分由1组成;这些都是除以1的整数.)

在给定间隔内逼近密度

使用OEIS页面上提供的公式,您可以计算或近似区间0-1中的密度,并乘以2这是可以表示为分数的唯一值的总数.

给定两个值s和t,然后可以计算和求和间隔s~s + 1,s + 1~s + 2,... t-1~t中的密度,或者使用插值来获得更快但是近似值不太精确.

假设我们使用的是10位整数,能够表示0到1023之间的值.使用从OEIS页面链接的这个表,我们发现0~1之间的密度是318452,并且总分数是636904 .

如果我们想在间隔s~t = 100~105中找到密度:

100~101: 1/2 x 1/100 x 1/101 = 1/20200 ; 636904/20200 = 31.53  
101~102: 1/2 x 1/101 x 1/102 = 1/20604 ; 636904/20604 = 30.91  
102~103: 1/2 x 1/102 x 1/103 = 1/21012 ; 636904/21012 = 30.31  
103~104: 1/2 x 1/103 x 1/104 = 1/21424 ; 636904/21424 = 29.73  
104~105: 1/2 x 1/104 x 1/105 = 1/21840 ; 636904/21840 = 29.16  
Run Code Online (Sandbox Code Playgroud)

舍入这些值得出总和:

32 + 31 + 30 + 30 + 29 = 152  
Run Code Online (Sandbox Code Playgroud)

蛮力算法给出了这样的结果:

32 + 32 + 30 + 28 + 28 = 150  
Run Code Online (Sandbox Code Playgroud)

因此,对于这个低M值和小间隔只有5个值,我们的偏差为1.33%.如果我们在第一个和最后一个值之间使用了线性插值:

100~101:  31.53  
104~105:  29.16  
average:  30.345
total:   151.725 -> 152
Run Code Online (Sandbox Code Playgroud)

我们已达到相同的价值.对于较大的区间,所有密度的总和可能会更接近实际值,因为舍入误差将相互抵消,但线性插值的结果可能会变得不那么准确.对于更大的M值,计算的密度应该与实际值收敛.


Φ(n)的逼近质量

使用这个简化的公式:

Φ(Ñ)=(3÷π 2Ñ 2

结果几乎总是小于实际值,但是对于n≥182,它们在1%以内,对于n≥1880,它们在0.1%之内,对于n≥19494,它们在0.01%之内.我建议硬编码较低范围(第一个这里可以找到50,000个值,然后使用从近似值足够好的点开始的简化公式.

Phi(n)的近似


这是一个简单的代码示例,其中Φ(n)的前182个值是硬编码的.分布序列的近似似乎增加了与Φ(n)的近似值相似的误差,因此应该可以得到合适的近似值.代码简单地遍历区间s~t中的每个整数并对分数求和.为了加速代码并仍然获得良好的结果,您应该计算区间中几个点的分数,然后使用某种非线性插值.

function fractions01(M) {
    var phi = [0,1,2,4,6,10,12,18,22,28,32,42,46,58,64,72,80,96,102,120,128,140,150,172,180,200,212,230,242,270,278,308,
               324,344,360,384,396,432,450,474,490,530,542,584,604,628,650,696,712,754,774,806,830,882,900,940,964,1000,
               1028,1086,1102,1162,1192,1228,1260,1308,1328,1394,1426,1470,1494,1564,1588,1660,1696,1736,1772,1832,1856,
               1934,1966,2020,2060,2142,2166,2230,2272,2328,2368,2456,2480,2552,2596,2656,2702,2774,2806,2902,2944,3004,
               3044,3144,3176,3278,3326,3374,3426,3532,3568,3676,3716,3788,3836,3948,3984,4072,4128,4200,4258,4354,4386,
               4496,4556,4636,4696,4796,4832,4958,5022,5106,5154,5284,5324,5432,5498,5570,5634,5770,5814,5952,6000,6092,
               6162,6282,6330,6442,6514,6598,6670,6818,6858,7008,7080,7176,7236,7356,7404,7560,7638,7742,7806,7938,7992,
               8154,8234,8314,8396,8562,8610,8766,8830,8938,9022,9194,9250,9370,9450,9566,9654,9832,9880,10060];
    if (M < 182) return phi[M];
    return Math.round(M * M * 0.30396355092701331433 + M / 4); // experimental; see below
}

function fractions(M, s, t) {
    var half = fractions01(M);
    var frac = (s == 0) ? half : 0;
    for (var i = (s == 0) ? 1 : s; i < t && i <= M; i++) {
        if (2 * i < M) {
            var f = Math.round(half / (i * (i + 1)));
            frac += (f < 2) ? 2 : f;
        }
        else ++frac;
    }
    return frac;
}

var M = 1023, s = 100, t = 105;
document.write(fractions(M, s, t));
Run Code Online (Sandbox Code Playgroud)


将Φ(n)的近似值与50,000个第一值的列表进行比较表明,添加M÷4是公式第二部分的可行替代品; 我没有对n的较大值进行测试,因此请谨慎使用.

Phi(n)的改进近似

蓝色:简化公式.红色:改进了简化公式.


分布近似的质量

将M = 1023的结果与蛮力算法的结果进行比较,误差实际上很小,从不超过-7或+6,而在205~206之间,它们被限制在-1~ + 1.然而,范围的大部分(57~1024)每个整数具有少于100个分数,并且在区间171~1024中,每个整数仅有10个分数或更少.这意味着-1或+1的小错误和舍入误差会对结果产生很大影响,例如:

interval: 241 ~ 250  
fractions/integer: 6  
approximation: 5  
total: 50 (instead of 60)  
Run Code Online (Sandbox Code Playgroud)

为了改善每个整数的分数很少的区间的结果,我建议将上述方法与该范围的最后部分的单独方法相结合:

范围的最后部分的替代方法

如已经提到的,并且在代码示例中实现,范围的后半部分M÷2~M,每个整数具有1个分数.另外,间隔M÷3~M÷2有2; 区间M÷4~M÷3有4.这当然是Φ(n)序列:

 M/2 ~  M  :   1  
 M/3 ~  M/2:   2  
 M/4 ~  M/3:   4  
 M/5 ~  M/4:   6  
 M/6 ~  M/5:  10  
 M/7 ~  M/6:  12  
 M/8 ~  M/7:  18  
 M/9 ~  M/8:  22  
M/10 ~  M/9:  28  
M/11 ~ M/10:  32  
M/12 ~ M/11:  42  
M/13 ~ M/12:  46  
M/14 ~ M/13:  58
M/15 ~ M/14:  64  
M/16 ~ M/15:  72  
M/17 ~ M/16:  80  
M/18 ~ M/17:  96  
M/19 ~ M/18: 102 ...  
Run Code Online (Sandbox Code Playgroud)

在这些间隔之间,一个整数可以具有不同数量的分数,具体取决于M的确切值,例如:

interval   fractions

202 ~ 203     10
203 ~ 204     10
204 ~ 205      9
205 ~ 206      6
206 ~ 207      6
Run Code Online (Sandbox Code Playgroud)

区间204~205位于区间之间的边缘,因为M÷5 = 204.6; 它有6 + 3 = 9个分数,因为M模5是3.如果M是1022或1024而不是1023,它将有8或10个分数.(这个例子很简单,因为5是素数;见下文.)

同样,我建议使用Φ(n)的硬编码值来计算范围最后部分的分数.如果使用上面列出的前17个值,则这将覆盖范围的每个整数少于100个分数的部分,这样可以减少舍入误差低于1%的影响.前56个值将给出0.1%,前182个值为0.01%.

与Φ(n)的值一起,您可以对每个模数值的边缘间隔的分数进行硬编码,例如:

modulo:  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17

M/ 2     1   2
M/ 3     2   3   4
M/ 4     4   5   5   6
M/ 5     6   7   8   9  10
M/ 6    10  11  11  11  11  12
M/ 7    12  13  14  15  16  17  18
M/ 8    18  19  19  20  20  21  21  22
M/ 9    22  23  24  24  25  26  26  27  28
M/10    28  29  29  30  30  30  30  31  31  32
M/11    32  33  34  35  36  37  38  39  40  41  42
M/12    42  43  43  43  43  44  44  45  45  45  45  46
M/13    46  47  48  49  50  51  52  53  54  55  56  57  58
M/14    58  59  59  60  60  61  61  61  61  62  62  63  63  64
M/15    64  65  66  66  67  67  67  68  69  69  69  70  70  71  72
M/16    72  73  73  74  74  75  75  76  76  77  77  78  78  79  79  80
M/17    80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96
M/18    96  97  97  97  97  98  98  99  99  99  99 100 100 101 101 101 101 102
Run Code Online (Sandbox Code Playgroud)