【问题标题】:Density of fractions between 2 given numbers2个给定数字之间的分数密度
【发布时间】:2023-03-31 00:43:01
【问题描述】:

我正在尝试对一个简单的 Fraction 类进行一些分析,并且我想要一些数据来将该类型与 doubles 进行比较。

问题

知道我正在寻找一些获得两个数字之间分数密度的好方法。分数基本上是 2 个整数(例如 pair< long, long>),st 之间的密度是该范围内可表示数字的数量。并且它必须是在 O(1) 或非常快的时间内完成的精确或非常好的近似。

为了简单一点,假设我想要 s 和 t 之间的所有数字(不是分数)a/b,其中 0 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.

我已经想到了什么

一个非常幼稚的方法是循环遍历所有可能的分数,并计算那些无法简化的分数。比如:

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;
}

但是gcd() 非常慢,所以它不起作用。我也尝试做一些数学,但我没有得到任何好的结果。

解决方案

感谢@m69 的回答,我为Fraction = pair&lt;Long,Long&gt; 制作了这段代码:

//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);
}

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

结果

正如我在开始时解释的那样,我试图将Fractionsdouble 进行比较。这是Fraction = pair&lt;Long,Long&gt; 的结果(以及here 我是如何获得双打密度的):

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

【问题讨论】:

  • 您的问题很可能作为本网站的题外话而被关闭。如果您希望它被视为编程问题,这就是本网站的用途,请包括MCVE
  • 我可以在 C++ 中添加一个类 Fraction,即使你可以创建一个类,但这个问题对于每个语言或代码都是通用的。它也不是编程的主题,因为我正在尝试分析在程序中的很多情况下有用的数据类型,并且答案将被编程(这就是为什么我要求在 O(1) 或合理的快速地)。另外,这就是为什么我放弃循环遍历可能的分数并使用 gcd()。如您所见,这与编程无关。
  • 如果一个近似的答案是足够的,计算在a &gt;= s*ba &lt;= t*b行之间的MxM正方形部分的面积。
  • 这太过分了。此外,不是面积,而是您必须计算的那个正方形中的“点”。对于 M=6,在 0 和 1 之间给出 17 而不是 13,误差为 40%。除非你想出比我更好的公式。
  • @HighPerformanceMark:某事是否与数学有关并不是适用于 Stack Overflow 的标准。是否是特定的编程问题。这是一个特定的编程问题,也涉及数学。这是一个编程问题,因为它涉及编程中出现的特定约束,包括固定宽度算术施加的界限和性能问题(OP 要求的不是产生答案的数学函数,而是算法和快速算法,最好是 O(1))。

标签: c++ algorithm math fractions


【解决方案1】:

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 ...  

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

Φ(n) = (3 ÷ π2) × n2 + O[n × (ln n)2/3 × (ln ln n)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  

共有 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

不仅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 ...  

这个数列可以从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 ...  

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

0-1: 1/2  
6-7: 1/2 x 1/6 x 1/7 = 1/84  

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

给定区间内的近似密度

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

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

示例

假设我们使用 10 位整数,能够表示从 0 到 1023 的值。使用从 OEIS 页面链接的this table,我们发现 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  

将这些值四舍五入得出总和:

32 + 31 + 30 + 30 + 29 = 152  

蛮力算法给出了这个结果:

32 + 32 + 30 + 28 + 28 = 150  

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

100~101:  31.53  
104~105:  29.16  
average:  30.345
total:   151.725 -> 152

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


Φ(n)的近似质量

使用这个简化的公式:

Φ(n) = (3 ÷ π2) × n2

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


这是一个简单的代码示例,其中 Φ(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));

将 Φ(n) 的近似值与 50,000 个第一个值的列表进行比较表明,添加 M÷4 是公式第二部分的可行替代方案;我没有测试过更大的 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)  

为了改善每个整数有几个小数的区间的结果,我建议将上述方法与范围最后一部分的单独方法结合起来:

范围最后部分的替代方法

如前所述,并在代码示例中实现,范围的后半部分,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 ...  

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

interval   fractions

202 ~ 203     10
203 ~ 204     10
204 ~ 205      9
205 ~ 206      6
206 ~ 207      6

区间 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

【讨论】:

    【解决方案2】:

    这与以下内容完全相同:(Sum of phi(k)) where m &lt;= k &lt;= M where phi(k) is the Euler Totient Function and with phi(0) = 1(由问题定义)。这个总和没有已知的封闭形式。但是,在 wiki 链接中提到了许多已知的优化。这在 Wolfram 中称为Totient Summatory Function。同一网站还链接到该系列:A002088,并提供了一些渐近近似值。

    原因是这样的:考虑{1/M, 2/M, ...., (M-1)/M, M/M} 形式的值的数量。所有可归约为较小值的分数都不会计入phi(M),因为它们不是相对质数。它们将出现在另一个totient的总和中。

    例如,phi(6) = 12,你有1 + phi(6),因为你也计算了0

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多