假设我有一个实数。我想用整数a和b的形式a + sqrt(b)来近似它。但我不知道a和b的值。当然我更喜欢用a和b的小值得到一个很好的近似值。让我们暂时不确定“好”和“小”是什么意思。这些术语的任何合理定义都可以。
有找到它们的理智方式吗?像continued fraction algorithm这样的东西用于查找小数的小数近似值。有关分数问题的更多信息,请参阅here。
编辑:澄清一下,它是一个任意实数。我只有一堆数字。因此,根据我们想要的近似值有多好,a和b可能存在也可能不存在。蛮力自然不是一个特别好的算法。我能想到的最好的方法是开始向我的实数添加整数,对结果求平方,然后看看我是否接近整数。相当蛮力,而不是一个特别好的算法。但如果没有更好的存在,那么知道这本身就很有意思。
编辑:显然b必须为零或正数。但是a可以是任何整数。
答案 0 :(得分:6)
无需持续分数;只计算b
的所有“小”值的平方根(达到你认为仍然“足够小”的任何值),删除小数点前的所有内容,并将它们全部排序/存储(连同生成它的b
。)
然后,当您需要逼近实数时,找到小数部分与实数的小数部分相对应的基数。这会给你b
- 选择正确的a
就是一个简单的减法问题。
答案 1 :(得分:5)
这实际上是一个数学问题,而不是计算机问题,但回答这个问题,我认为你是对的,你可以使用连续分数。你所做的是首先将目标数字表示为连续分数。例如,如果您想近似pi(3.14159265),则CF为:
3:7,15,1,288,1,2,1,3,1,7,4 ......
下一步是为平方根创建一个CF表,然后将表中的值与目标值的小数部分进行比较(此处:7,15,1,288,1,2,1,3 ,1,7,4 ......)。例如,假设你的表只有1-99的平方根。然后你会发现最接近的匹配是sqrt(51),它的CF重复为7:7,14。 7,14最接近pi的7,15。因此你的答案是:
SQRT(51)-4
作为最接近的给定b
使用CF的优势在于它比使用双倍或使用浮点更快。例如,在上面的例子中,你只需要比较两个整数(7和15),你也可以使用索引来快速找到表中最近的条目。
答案 2 :(得分:1)
这可以非常有效地使用mixed integer quadratic programming完成(尽管没有运行时保证,因为MIQP是NP完全的。)
定义:
d := the real number you wish to approximate
b, a := two integers such that a + sqrt(b) is as "close" to d as possible
r := (d - a)^2 - b, is the residual of the approximation
目标是尽量减少r
。将您的二次程序设置为:
x := [ s b t ]
D := | 1 0 0 |
| 0 0 0 |
| 0 0 0 |
c := [0 -1 0]^T
with the constraint that s - t = f (where f is the fractional part of d)
and b,t are integers (s is not)
这是一个凸(因此是最优解)混合整数二次规划,因为D
是半正定的。
计算s,b,t
后,只需使用b=b
得出答案,s=d-a
即可忽略t
。
你的问题可能是NP完全的,如果是这样的话会很有趣。
答案 3 :(得分:1)
以前的一些答案使用时间或空间复杂度为O(n)的方法,其中n是将被接受的最大“小数”。相反,以下方法在时间上是O(sqrt(n)),在空间中是O(1)。
假设正实数r = x + y
,其中x=floor(r)
和0 ≤ y < 1
。我们希望使用r
形式的a + √b
近似x+y ≈ a+√b
。如果x+y-a ≈ √b
然后√b ≈ h+y
,那么h
表示某个整数偏移b ≈ (h+y)^2
和(h+y)^2
。为了使b成为整数,我们希望将h
的小数部分最小化为所有符合条件的√n
。最多h
符合条件的import math, random
def findb(y, rhi):
bestb = loerror = 1;
for r in range(2,rhi):
v = (r+y)**2
u = round(v)
err = abs(v-u)
if round(math.sqrt(u))**2 == u: continue
if err < loerror:
bestb, loerror = u, err
return bestb
#random.seed(123456) # set a seed if testing repetitively
f = [math.pi-3] + sorted([random.random() for i in range(24)])
print (' frac sqrt(b) error b')
for frac in f:
b = findb(frac, 12)
r = math.sqrt(b)
t = math.modf(r)[0] # Get fractional part of sqrt(b)
print ('{:9.5f} {:9.5f} {:11.7f} {:5.0f}'.format(frac, r, t-frac, b))
值。请参阅以下python代码和示例输出。
findb()
(注1:此代码为演示形式; y
的参数为r
,rhi
的小数部分和if round(math.sqrt(u))**2 == u: continue
,平方根为最大的小数字。您可能希望更改参数的使用。注2:
findb()
代码行阻止b
返回b=51
的完美平方值,但值b = 1除外,因为没有完美的平方可以提高b = 1提供的精度。)
示例输出如下。中间已经排除了大约十几条线。第一个输出行显示此过程产生pi
来表示 frac sqrt(b) error b
0.14159 7.14143 -0.0001642 51
0.11975 4.12311 0.0033593 17
0.12230 4.12311 0.0008085 17
0.22150 9.21954 -0.0019586 85
0.22681 11.22497 -0.0018377 126
0.25946 2.23607 -0.0233893 5
0.30024 5.29150 -0.0087362 28
0.36772 8.36660 -0.0011170 70
0.42452 8.42615 0.0016309 71
...
0.93086 6.92820 -0.0026609 48
0.94677 8.94427 -0.0024960 80
0.96549 11.95826 -0.0072333 143
0.97693 11.95826 -0.0186723 143
的小数部分,这与其他一些答案中报告的值相同。
frac, rhi = math.pi-3, 16
print (' frac sqrt(b) error b bMax')
while rhi < 1000:
b = findb(frac, rhi)
r = math.sqrt(b)
t = math.modf(r)[0] # Get fractional part of sqrt(b)
print ('{:11.7f} {:11.7f} {:13.9f} {:7.0f} {:7.0f}'.format(frac, r, t-frac, b,rhi**2))
rhi = 3*rhi/2
frac sqrt(b) error b bMax
0.1415927 7.1414284 -0.000164225 51 256
0.1415927 7.1414284 -0.000164225 51 576
0.1415927 7.1414284 -0.000164225 51 1296
0.1415927 7.1414284 -0.000164225 51 2916
0.1415927 7.1414284 -0.000164225 51 6561
0.1415927 120.1415831 -0.000009511 14434 14641
0.1415927 120.1415831 -0.000009511 14434 32761
0.1415927 233.1415879 -0.000004772 54355 73441
0.1415927 346.1415895 -0.000003127 119814 164836
0.1415927 572.1415909 -0.000001786 327346 370881
0.1415927 911.1415916 -0.000001023 830179 833569
在程序结束时添加以下代码,也会出现如下所示的输出。这显示了pi的小数部分的更接近的近似值。
{{1}}
答案 4 :(得分:0)
我不知道这种问题是否有任何标准算法,但它确实引起了我的兴趣,所以这是我尝试开发一种找到所需近似值的算法。
拨打有问题的真实电话号码r
。然后,首先我假设a
可以是负数,在这种情况下我们可以减少问题,现在只需找到b
,使得sqrt(b)
的小数部分是一个很好的近似值r
的小数部分。现在让我们将r
写为r = x.y
,x
为整数,y
为小数部分。
Now:
b = r^2
= (x.y)^2
= (x + .y)^2
= x^2 + 2 * x * .y + .y^2
= 2 * x * .y + .y^2 (mod 1)
我们现在只需查找x
0 = .y^2 + 2 * x * .y (mod 1)
(大约)。
将x
填入上面的公式后,我们得到b
,然后可以将a
计算为a = r - b
。 (当然,所有这些计算都必须仔细整理。)
现在,暂时我不确定是否有办法找到这个x
而没有暴力强迫它。但即使这样,人们也可以简单地使用一个简单的循环来找到x
足够好的东西。
我正在考虑这样的事情(半伪代码):
max_diff_low = 0.01 // arbitrary accuracy
max_diff_high = 1 - max_diff_low
y = r % 1
v = y^2
addend = 2 * y
x = 0
while (v < max_diff_high && v > max_diff_low)
x++;
v = (v + addend) % 1
c = (x + y) ^ 2
b = round(c)
a = round(r - c)
现在,我认为这种算法非常有效,甚至允许您指定近似的所需精度。可以将其转换为O(1)算法的一件事是计算所有x
并将它们放入查找表中。如果只关心r
的前三个十进制数字(例如),查找表将只有1000个值,即只有4kb的内存(假设使用了32位整数)。
希望这对你有帮助。如果有人发现算法有任何问题,请在评论中告诉我,我会解决它。
修改强>
经过反思,我撤回了我的效率主张。事实上,就我无法保证上述算法将永久终止而言,即使确实如此,也可能需要很长时间才能找到一个能够充分解决方程的非常大的x
。
可以跟踪到目前为止发现的最佳x
,并随着时间的推移放宽准确度界限,以确保算法快速终止,但可能会降低准确性。
如果只是预先计算一个查找表,这些问题当然是不存在的。