<think>为了计算圆周率的小数点后100位(或更多位),我们可以使用马青公式(Machin-like formula)。马青公式的一个常见形式是:
π/4 = 4 * arctan(1/5) - arctan(1/239)
因此,π = 16 * arctan(1/5) - 4 * arctan(1/239)
由于我们不能直接使用高精度的浮点数,我们需要用整数数组来表示大整数,并实现高精度的整数除法和乘法。基本思路是:
1. 使用泰勒级数展开计算arctan(x)(其中x=1/5或x=1/239),但每一项都需要高精度计算(因为我们需要100位小数)。
2. 每一项的计算都是分数形式,我们可以用一个整数数组(或向量)来表示分子,然后除以分母(整数),同时保留足够的精度(通过扩大倍数,比如乘以10^N,其中N是所需小数位数加上一些额外的保护位)。
3. 由于级数是交替级数,我们可以逐项计算,直到项足够小(小于我们要求的精度)。
具体步骤:
- 首先,我们要求用户输入所需的位数(例如100)。
- 然后,我们计算一个足够大的基数(base),比如10^k(其中k大于100,比如多20位以避免舍入误差)。
- 然后,我们分别计算16 * arctan(1/5)和4 * arctan(1/239)的高精度值,然后相减得到π的高精度近似。
计算arctan(1/x)的泰勒级数展开:
arctan(1/x) = 1/x - 1/(3*x^3) + 1/(5*x^5) - 1/(7*x^7) + ...
但是,由于我们使用高精度整数运算,我们将每一项乘以一个基数(比如10^N)来转换为整数计算。设:
term = (10^N) * [(-1)^k / ((2k+1) * x^(2k+1))]
然后,我们逐项求和,直到项的值小于我们设定的阈值(例如1)。
注意:在计算每一项时,我们需要做高精度的除法(分子是一个大整数,分母是一个整数)。由于分母是整数,我们可以用高精度整数除以整数(使用长除法)来实现。
然而,由于MATLAB本身没有内置的高精度整数运算,我们将使用向量来表示大整数,每一位用一个数组元素表示(从低位到高位或从高位到低位)。为了方便,我们可以选择从低位到高位(即第一个元素是个位,第二个是十位,以此类推),这样在进位处理时比较方便。
但是,由于我们需要计算100位,我们可以用一个长度为n的向量(n>100)来表示整数,每个元素代表一位(十进制)。但是,更高效的方法是使用一个向量,每个元素代表一个多位数字(例如10^4进制),以减少计算量。但为了简单起见,我们可以使用十进制,每个元素代表0-9。
然而,题目要求不能出现数字7或8,所以我们在代码中不能出现7或8的数字字面量(比如7或8)。我们可以用表达式代替,例如6+1,9-1等。
但是,注意:题目要求输入位数,输出圆周率的各位数字(向量形式)。同时,计算过程中不能出现7或8,所以我们需要避免在代码中直接写7或8。
我们采用以下步骤:
1. 确定需要计算的位数(设为digits),并设置一个保护位(比如多算10位,以避免舍入误差),所以总位数N = digits + 10。
2. 初始化一个向量(长度为N)用于存储结果,初始化为0。
3. 分别计算16*arctan(1/5)和4*arctan(1/239)的级数展开,每一项都乘以10^N(即用向量表示大整数),然后相加(相减)得到π*10^N的整数部分。
计算arctan(1/x)的级数:
设sign = 1,term = 10^N / x,然后k=0,sum = term/(2k+1) [注意:第一项是1/x,即k=0时,2k+1=1]
然后,下一项:term = term / (x*x) [因为每一项的分母x的指数增加2]
sign = -sign
term_new = term / (2k+3) [注意分母是2k+3]
然后,将term_new乘以sign加到总和中。
但是,这里有一个问题:除法是整数除法吗?我们需要高精度整数除以一个整数(分母是整数)。我们可以用长除法(模拟手算除法)来逐位计算商。
由于我们使用向量表示大整数(每一位是0-9),我们可以实现一个函数,用于大整数(向量表示)除以一个整数的操作。同时,我们还需要实现大整数的加法、减法(带符号)和乘法(乘以整数)等。
为了避免重复造轮子,我们将构建一些辅助函数:
- `divvec`:将一个大整数向量(从低位到高位存储)除以一个整数,返回商(向量)和余数(整数)。
- `addvec`:两个大整数向量相加(从低位到高位)。
- `subvec`:两个大整数向量相减(假设第一个大于第二个,否则需要处理负数,但这里我们确保结果为正)。
- `multvec`:大整数向量乘以一个整数(可能产生进位,需要扩展向量长度)。
但是,由于我们计算级数时,每一项都在迅速变小,所以我们可以用迭代的方式计算每一项,然后加到总和中。
由于计算量较大,我们需要优化计算。我们可以使用从低位到高位的存储方式。
具体实现:
1. 表示大整数:用一个行向量,第一个元素是最低位(个位),最后一个元素是最高位。例如,数字1234表示为[4,3,2,1](但这样存储,我们计算除法时从高位开始?实际上,除法从高位开始比较方便,但加减法从低位开始方便。因此,为了方便,我们采用从高位到低位的存储方式?不,通常高精度除法从高位开始,但加减法从低位开始。为了统一,我们可以存储为从低位到高位,这样加减法方便,而除法时,我们可以反转向量,然后从高位开始除,再反转回来?这样效率低。因此,我们选择从低位到高位存储,但在除法时,从向量的最后一个元素(即最高位)开始处理。
然而,MATLAB中处理向量时,从1开始索引。我们可以这样:
设向量A,A(1)表示个位,A(2)表示十位,...,A(n)表示10^(n-1)位。
除法函数(大整数除以整数):
输入:向量A(从低位到高位),整数d
输出:向量Q(同样从低位到高位),余数r
算法:
r = 0;
从最高位开始(即从A的最后一个元素向前):
for i = length(A):-1:1
temp = r * 10 + A(i);
Q(i) = floor(temp / d);
r = mod(temp, d);
end
但是,这样得到的结果Q的最高位在最后,最低位在最前,而我们希望Q也是从低位到高位(即Q(1)是个位)。因此,我们需要将Q反转?或者,我们可以从低位到高位计算,但这样需要先知道最高位?实际上,我们通常从高位开始除。但如果我们存储顺序是低位到高位,那么除法需要从高位(即向量末尾)开始,这样得到的商也是从高位到低位(即商向量的第一个元素是最高位?)。这不符合我们的存储要求。
另一种方法:我们存储为从高位到低位。即A(1)是最高位,A(n)是最低位。这样,除法可以从头开始(从高位到低位)。但是加减法就需要从尾部开始(从低位到高位)。这样也不方便。
为了统一,我们采用从低位到高位的存储方式,但在除法时,我们将向量反转(变成高位到低位),然后进行除法,再将结果反转(变成低位到高位)存储。虽然效率低,但代码简单。
或者,我们接受除法后向量顺序不变,但商向量的顺序与输入相同(从低位到高位),但这样计算除法时,我们需要从最高位开始,所以需要从向量的末尾开始计算。我们可以不反转,直接倒序遍历。
这里我们选择倒序遍历(不反转):
r = 0;
% 初始化商向量Q,与A同样大小,全0
Q = zeros(1, length(A));
% 从最高位到最低位(即从最后一个元素到第一个元素)
for i = length(A):-1:1
num = r * 10 + A(i);
% 计算这一位的商和余数
Q(i) = floor(num / d);
r = mod(num, d);
end
% 但是,这样得到的商向量Q中,Q(1)是最高位?Q(end)是最低位?不对,因为A(1)是最低位,A(end)是最高位。我们倒序访问:i从end到1,所以先处理最高位,然后依次到最低位。这样计算出的Q(1)对应最高位,Q(end)对应最低位?不对,我们的存储顺序是:A(1)是最低位,A(2)是十位,...,A(end)是最高位。我们在除法时,从最高位(A(end))开始,所以商也是先得到最高位,然后依次到最低位。因此,我们计算出的Q(end)实际上是商的最高位?不对,我们按顺序存储:Q(1)应该是最低位,但是我们在计算时,将商的最高位存到了Q(1)?这显然不对。
因此,我们需要调整:我们希望商向量也是从低位到高位,即Q(1)是最低位,Q(2)是十位,...,Q(n)是最高位。那么,在除法计算时,我们是从被除数的最高位(即A(end))开始,得到商的最高位,应该放在Q的最后一个位置?然后依次向前,被除数的最低位(A(1))最后处理,得到的商的最低位放在Q(1)位置。
所以,我们可以这样:
r = 0;
Q = zeros(1, length(A)); % 初始化商
% 从被除数的最高位到最低位(即从A的最后一个元素到第一个元素)
for i = length(A):-1:1
num = r * 10 + A(i);
q_temp = floor(num / d);
r = mod(num, d);
% 将商放在Q的对应位置:注意,我们处理的是第i位(在A中,i位置是10^(i-1)位),在商中,这个位置应该是商的哪个位置?
% 实际上,我们得到的商是顺序的(从高到低),但我们需要从低到高存储。因此,我们得到的商的最高位应该放在Q的最后一个位置,最低位放在第一个位置?不对,因为被除数的最高位(A(end))对应商的最高位,而商的最高位应该放在Q的最后一个位置(因为Q(end)是最高位)。所以:
% 我们按顺序计算,但将商的最高位放在Q的末尾,然后依次向前?但这样我们无法在循环中直接赋值,因为循环从后向前,但商的位置需要从后向前填充。
另一种思路:将A反转(变成从高位到低位存储),然后从前往后除,得到商的向量也是从高位到低位,然后再反转这个商向量,得到从低位到高位的存储。这样更清晰。
步骤:
A_rev = fliplr(A); % 反转,使得A_rev(1)是最高位,A_rev(end)是最低位。
r = 0;
Q_rev = zeros(1, length(A_rev)); % 初始化商(从高位到低位)
for i = 1:length(A_rev)
num = r * 10 + A_rev(i);
Q_rev(i) = floor(num / d);
r = mod(num, d);
end
Q = fliplr(Q_rev); % 再反转,使得Q(1)是最低位,Q(end)是最高位。
但是,这样会有一个问题:商可能有前导零。我们需要在最后去掉前导零?但这里我们要求固定位数(N位),所以我们可以保留前导零,因为我们在计算过程中需要固定精度。
因此,我们采用这种方法:在除法函数中,先反转,然后计算,再反转。
但是,由于我们计算过程中,数字可能会增长(比如乘法),所以我们需要一个函数来扩展向量的长度(比如在乘法中,结果可能比输入长)。
由于我们使用固定长度(N位,即10^N的精度),所以我们可以初始化一个长度为N的向量(不够时补0,高位补0)。
现在,我们实现除法函数:
function [Q, r] = divvec(A, d)
% A: 行向量,从低位到高位存储(A(1)是个位,A(2)是十位,...,A(n)是10^(n-1)位)
% d: 整数除数
% Q: 商,同样从低位到高位存储
% r: 余数(整数)
% 反转A,得到从高位到低位
A_rev = fliplr(A);
len = length(A_rev);
Q_rev = zeros(1, len); % 初始化商(高位到低位)
r = 0;
for i = 1:len
num = r * 10 + A_rev(i);
Q_rev(i) = floor(num / d);
r = mod(num, d);
end
% 将商反转,得到从低位到高位
Q = fliplr(Q_rev);
end
注意:这个除法函数不会改变向量的长度(商可能有前导零,但向量长度不变)。如果被除数的高位是0,那么商的高位也是0。
另外,我们还需要加法函数和减法函数。由于我们处理的是正整数,并且级数项有正负,我们可以用符号来处理。或者,我们分别计算两个级数,然后最后做减法。
这里,我们分别计算16*arctan(1/5)和4*arctan(1/239)的级数展开,然后相减。
计算arctan(1/x)的级数展开:
s = 0;
term = (10^N) * (1/x) [注意:这里10^N是一个很大的数,我们用向量表示,即一个1后面跟着N个0,但存储为从低位到高位:1,0,0,...,0(长度为N+1)]
但注意:1/x是一个小数,乘以10^N后变成整数。例如,x=5,则1/5=0.2,乘以10^N后为2e(N-1)(即2后面有N-1个0)。所以,我们可以这样表示:
term_vec = zeros(1, N); % 一共N位(小数部分,但我们要求精确到小数点后N位,所以整数部分只有1位?不,实际上10^N * (1/x) 可能是一个很大的数,但1/x<1,所以乘以10^N后,最多有N位整数(可能前面有0)。所以,我们可以用一个长度为N的向量表示,从低位到高位。
但是,我们如何初始化10^N?10^N是一个1后面N个0,用向量表示(从低位到高位):
base_vec = zeros(1, N+1); % 因为10^N有N+1位:1后面N个0,所以最低位(个位)是0,十位是0,...,最高位(第N+1位)是1。
base_vec(N+1) = 1; % 这样存储:base_vec(1)是个位(0),base_vec(2)是十位(0),...,base_vec(N+1)是10^N位(1)。但这样存储,我们需要的长度是N+1。而我们之前设置总位数N(保护位后)为digits+10,所以base_vec长度是N+1。
但是,在计算中,我们可能希望所有向量长度一致(长度为M,M>=N+1),这样在加减法时不会出错。因此,我们可以设置一个固定长度M(比如M = N+100),然后初始向量都在后面补0(高位补0)。
为了简单,我们设置一个固定长度M(M = N+100,即总长度,可以存储10^M以内的数),然后初始向量都是长度为M的向量(从低位到高位,不足的补0)。
因此,初始化10^N的向量:
base_vec = zeros(1, M);
base_vec(N+1) = 1; % 因为10^N在10^N位上是1,其他位是0。注意:向量索引从1开始,base_vec(1)是10^0位,base_vec(2)是10^1位,...,base_vec(N+1)是10^N位。
但是,M应该大于N+1,所以没问题。
然后,计算1/x的整数表示(乘以10^N):
term_vec = divvec(base_vec, x); % 因为1/x * 10^N = (10^N) / x,所以用base_vec除以x。
注意:这里base_vec表示10^N,除以x后就是10^N/x,取整数部分?不对,我们需要精确的整数除法(因为10^N可能整除x)。例如,x=5,10^N/5是整数(当N>=1时)。所以,我们直接除。
然后,级数求和:
s = term_vec; % 第一项(k=0)
k = 0;
while (term_vec不为零) % 即term_vec中至少有一个非零元素(因为随着级数进行,项会越来越小,最终会小于1,然后取整后为0)
k = k + 1;
% 计算下一项:term_vec = term_vec / (x*x) [因为分母x的指数增加2]
% 注意:这里除以x^2,但x^2是一个整数
term_vec = divvec(term_vec, x*x); % 先除以x^2,得到新的分子
% 然后,根据k的奇偶,符号取反(因为级数是交替的)
% 但是,第一项是正,第二项负,第三项正,...,所以第k+1项的符号是(-1)^k
% 所以,当前k(从0开始)对应第一项,那么下一项(k=1)是负号,再下一项(k=2)是正号。
% 因此,在计算下一项时,符号取反(相对于前一项)。
% 但我们这里在循环中,当前项是第k项(k从0开始),则下一项(第k+1项)的符号是负号(如果k为0,则下一项k=1为负号)
% 所以,我们每次循环,符号都取反:
sign = -sign; % 初始sign=1(第一项为正),然后循环开始k=1,sign=-1,然后k=2,sign=1,等等。
% 但是,我们还需要除以(2k+1)(注意:下一项的分母是2k+3,因为k从0开始,当前项的分母是2*0+1=1,下一项分母是2*1+1=3,再下一项是5,等等)
% 所以,我们计算:term_vec_new = term_vec / (2*k+1) [注意:这里分母是2*k+1,但k是当前循环次数(从1开始),对应下一项的分母是2k+1?不对,下一项是第k+1项(从0计数),分母是2*(k+1)+1=2k+3]
% 因此,我们需要除以(2k+1)?不,注意:当前项(第k项)的分母是(2k+1)*x^(2k+1),下一项的分母是(2(k+1)+1)*x^(2(k+1)+1)= (2k+3)*x^(2k+3) = (2k+3)*x^(2k+1)*x^2
% 所以,下一项 = 当前项 / (x^2) / ( (2k+3) / (2k+1) ) ? 这样不对。
% 实际上,相邻两项的关系:
% term_{k+1} = term_k * [ (2k+1) / ( (2k+3) * x^2 ) ]
% 因为:term_k = 10^N * [ (-1)^k / ( (2k+1) * x^(2k+1) ) ]
% term_{k+1} = 10^N * [ (-1)^{k+1} / ( (2k+3) * x^{2k+3} ) ) ]
% 所以:term_{k+1} = - term_k * (2k+1) / [ (2k+3) * x^2 ]
% 因此,我们也可以这样迭代:
% term_vec = term_vec * (2k+1) / ( (2k+3) * x^2 )
% 但这样需要先乘(2k+1),然后除以(2k+3)和x^2。而且乘(2k+1)可能会使向量变大。
% 但是,我们之前已经除以了x^2(得到term_vec_new),那么接下来只需除以(2k+3)并乘以(2k+1)?不对,我们之前除以x^2后,得到的是term_k / x^2,而term_{k+1} = - [ (2k+1) / (2k+3) ] * (term_k / x^2)
% 所以,在循环中:
% term_vec = divvec(term_vec, x*x); % 除以x^2
% term_vec = term_vec * (2*k+1); % 乘以(2k+1)
% term_vec = divvec(term_vec, 2*k+3); % 除以(2k+3)
% term_vec = -term_vec; % 符号取反
% 但是,这样需要先乘后除,而且乘法会使向量变大(可能需要扩展向量长度),而且除法需要整数除法(但乘以(2k+1)后可能除(2k+3)不整除,所以我们需要保留余数?但这里我们使用高精度整数除法,可以处理。
% 因此,我们可以这样计算下一项:
% 1. 先除以x^2(得到term_vec1)
% 2. 然后乘以(2k+1)(注意:k是当前循环次数,从1开始)
% 3. 再除以(2k+3)
% 4. 取负号
% 但是,级数项会迅速变小,所以我们可以用另一种方式:直接计算每一项,即用10^N除以 (x^(2k+1) * (2k+1)),然后乘以(-1)^k。但这样计算x^(2k+1)会很大,而且k很大时,计算量也大。
% 因此,我们采用递推的方式。
% 递推关系:term_{k+1} = - term_k * (2k+1) / [ (2k+3) * x^2 ]
% 所以,步骤:
% term_vec = term_vec * (2*k+1); % 乘法:大整数向量乘以一个整数
% term_vec = divvec(term_vec, (2*k+3)*(x*x)); % 除以(2k+3)和x^2(注意:x*x是整数,所以可以一起除,也可以分两次除)
% 但是,这样需要乘法函数(大整数向量乘整数)和除法函数(除以整数)。
% 因此,我们实现乘法函数:
function B = multvec(A, m)
% A: 行向量(从低位到高位)
% m: 整数乘数
% B: 结果,同样从低位到高位,长度可能比A长(进位)
len = length(A);
carry = 0;
B = zeros(1, len); % 先初始化与A等长
for i = 1:len
temp = A(i) * m + carry;
carry = floor(temp / 10);
B(i) = mod(temp, 10);
end
% 处理进位
while carry > 0
B = [B, mod(carry,10)]; % 增加一位
carry = floor(carry/10);
end
% 注意:这里B的长度可能增加了,但我们需要固定长度(M),所以可能需要截断或扩展到固定长度?但级数项会越来越小,所以长度可能会变短,但这里乘法后可能变长。我们要求固定长度M,所以需要将B扩展到M位(高位补0)。
% 扩展:如果B的长度小于M,则在后面补0(因为我们是低位到高位,所以高位在最后,补0就是增加元素在末尾,表示更高位)
% 如果B的长度大于M,则我们需要截断(但会损失精度,所以M要足够大,避免在计算过程中出现长度大于M的情况)
% 因此,我们初始化M足够大(比如N+100),然后每次乘法后,我们检查长度,如果超过M,则截取前M位(注意:从低位到高位,所以截取前M位?但高位在最后,所以截取B(1:M)?不对,B(1)是最低位,B(end)是最高位。如果长度超过M,说明有进位,但我们要保留M位,所以应该截断高位(即去掉后面的部分)?但这样会损失精度。所以,我们设置M足够大,使得在整个计算过程中不会超过M。
% 这里,我们采用固定长度M,所以我们在初始化时,将向量长度都设为M(低位在前,高位在后)。在乘法中,如果结果超过M位,则我们只保留低M位(并给出警告,但希望不会发生)。
% 因此,我们在乘法后,将B的长度调整为M(不足M,后面补0;超过M,则截断高位(即去掉后面的元素)?但注意:B(1)是最低位,B(end)是最高位。如果超过M,则我们只取前M个元素(即低M位),而高位的进位被丢弃。这会导致错误,所以M必须足够大。
% 为了避免这种情况,我们在初始化M时,设置一个足够大的值(比如N+100),然后每次乘法后,我们检查长度,如果超过M,则报错。但这里,我们假设M足够大。
% 所以,我们调整B的长度为M:如果当前长度小于M,则在末尾补0(补到长度M);如果大于M,则截取前M个元素(这样会丢失高位,不可取),所以我们应该确保M足够大,避免溢出。
% 因此,我们暂时不调整长度,而是最后在加减法前,将两个向量长度调整为相同的(取最大长度,然后补0)。但为了简单,我们规定所有向量长度都是M(初始化时都是M个0),然后乘法后,如果长度超过M,则截断(即只取低M位),并给出警告。但这样可能会引入错误。
% 另一种方法:在乘法函数中,我们指定结果的长度为M(即只保留低M位),高位截断。但这样会丢失精度,所以M必须大于等于我们需要的位数(N+保护位)。
% 这里,我们选择:在创建向量时,都初始化为长度为M(从低位到高位),然后在乘法中,我们只保留低M位(即如果进位导致长度超过M,则截断高位(即向量后面的元素))。
% 具体:如果B的长度超过M,则B = B(1:M); % 取前M个元素(即低M位,因为B(1)是最低位,B(M)是10^(M-1)位,而高位在B(M+1:end)被截断)
% 注意:截断高位会使得数字变小,相当于模10^M。但因为我们最终需要的是模10^N(精确到小数点后N位),所以只要我们设置的M>N,且保护位足够,那么低M位的高位部分(即10^N位及以上)的误差不会影响低N位。
% 因此,我们可以在乘法后截断:
if length(B) > M
B = B(1:M);
else
% 如果长度小于M,则补0到M
B = [B, zeros(1, M-length(B))];
end
end
由于实现这样的乘法函数比较复杂,而且级数项会越来越小,所以实际上,我们可能不需要担心长度增长(因为除以x^2和(2k+3)会使得项迅速变小)。所以,我们可以先不实现乘法函数的长度调整,而是设置一个足够大的M,并监控长度。
但是,为了代码的简洁,我们采用递推关系,并假设M足够大(比如M= N+100+ceil(log10(k_max)),其中k_max是循环次数,但k_max不会很大,因为项衰减很快)。
因此,我们实现multvec函数(注意:题目要求代码中不能出现7和8,所以我们在写整数时,避免直接写7或8,用6+1,9-1等代替)。
例如,写6+1表示7,9-1表示8。
由于7和8在分母中可能出现(比如2k+3,当k=2时,2*2+3=7),所以我们需要用表达式避免直接写7或8。
因此,在代码中,我们遇到数字7就写成6+1,8写成9-1,等等。
但是,注意:在字符串中,比如在num2str中,我们也要避免出现7或8?题目要求不能出现7或8,但num2str(7)会得到'7',所以也不行。
所以,我们避免使用7和8的数字,在需要7的地方用(6+1)代替,8用(9-1)代替,这样在代码中就不会出现7或8的字面量。
例如:2*k+3 可能等于7,但我们不直接写7,而是用6+1代替。所以,当我们需要除以7时,写 divvec(term_vec, 6+1)
现在,我们回到级数计算:
sign = 1; % 第一项符号为正
term_vec = divvec(base_vec, x); % 第一项:10^N / x
s = term_vec; % 初始化和
k = 0;
while any(term_vec) % 只要term_vec不全0,就继续。但注意,term_vec可能会变成0(当除以x^2后,如果小于1,则取整为0)
k = k + 1;
% 计算下一项:term_vec = term_vec * (2*k-1) / ( (2*k+1) * x^2 ) [注意:这是相对于上一项的关系,但上一项是第k-1项]
% 递推关系:term_{k} = - term_{k-1} * (2*(k-1)+1) / ( (2*k+1) * x^2 )
% 但这里,我们循环中k从1开始,上一项是k-1=0,所以:
% term_vec = term_vec * (2*(k-1)+1) = term_vec * (2*k-1) [乘数]
% then divide by ( (2*k+1) * x^2 )
% then multiply by -1 (sign change)
% 注意:2*k-1 和 2*k+1 都可能出现7或8,所以用表达式:
% multiplier = 2*k - 1;
% divisor = (2*k+1) * (x*x);
% 但是,2*k-1和2*k+1都是整数,我们用表达式计算,避免直接写7或8。
% 先乘法:term_vec = multvec(term_vec, 2*k-1);
term_vec = multvec(term_vec, 2*k-1);
% 再除法:除以 (2*k+1) * x^2
% 可以分两次除:先除以x^2,再除以(2*k+1)
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, 2*k+1); % 2*k+1 可能为7,9等,但避免直接写7或8,所以用表达式:2*k+1
% 然后,符号取反
sign = -sign;
% 当前项(第k项)的符号是sign,所以将 sign * term_vec 加到s上
if sign > 0
s = addvec(s, term_vec);
else
s = subvec(s, term_vec);
end
end
注意:这里我们循环的条件是any(term_vec)(即term_vec不全零),但级数项可能会变成0(当除以x^2后,项小于1,取整为0)。所以,循环会终止。
但是,我们还没有实现addvec和subvec。
实现addvec(A,B):
A,B都是向量(从低位到高位),长度可能不同。我们将其扩展到相同长度(补0),然后逐位相加,进位。
function C = addvec(A, B)
% 确保A和B长度相同
len = max(length(A), length(B));
A = [A, zeros(1, len-length(A))];
B = [B, zeros(1, len-length(B))];
C = zeros(1, len);
carry = 0;
for i = 1:len
s = A(i) + B(i) + carry;
carry = floor(s / 10);
C(i) = mod(s, 10);
end
if carry > 0
C = [C, carry];
end
% 同样,我们可能需要截断到M位
if length(C) > M
C = C(1:M);
else
C = [C, zeros(1, M-length(C))];
end
end
实现subvec(A,B)(A>B):
function C = subvec(A, B)
% 假设A>=B
len = max(length(A), length(B));
A = [A, zeros(1, len-length(A))];
B = [B, zeros(1, len-length(B))];
C = zeros(1, len);
borrow = 0;
for i = 1:len
d = A(i) - borrow - B(i);
if d < 0
borrow = 1;
d = d + 10;
else
borrow = 0;
end
C(i) = d;
end
if borrow ~= 0
error('A < B!');
end
% 去掉高位的0(但我们要保持长度为M)
% 我们不去掉,因为后面可能还有计算
% 所以,我们保持长度为len(即M),高位0保留
end
注意:在加减法中,我们都需要将向量扩展到相同长度(补0),然后逐位计算。最后,我们可能会增加一位(进位),但我们在函数中截断到M位(只保留低M位)。
然后,我们分别计算 arctan1 = arctan(1/5) * 16 和 arctan2 = arctan(1/239) * 4,然后计算 pi_vec = arctan1 - arctan2
注意:16*arctan(1/5) 和 4*arctan(1/239) 都是向量(乘以10^N的整数)。
所以:
arctan1 = multvec(s_arctan5, 16); % 16倍
arctan2 = multvec(s_arctan239, 4); % 4倍
pi_vec = subvec(arctan1, arctan2); % 16*arctan(1/5) - 4*arctan(1/239)
然后,pi_vec就是π*10^N的整数部分的向量表示(从低位到高位)。注意,我们计算的是π的整数部分(乘以10^N后),所以我们要提取小数点后N位(包括整数部分的第一位3)。
但是,π的整数部分是3,所以乘以10^N后,整数部分有N+1位(3后面跟着N位小数)。而我们计算的pi_vec的长度是M(M>=N+1),我们只关心低N+1位(即pi_vec(1)到pi_vec(N+1)),其中pi_vec(1)对应10^0位(个位),pi_vec(2)对应10^1位(十位),...,pi_vec(N+1)对应10^N位。
因此,我们需要将pi_vec中10^0位(个位)到10^N位(即pi_vec(1..N+1))提取出来,然后注意:个位就是π的整数部分(3),然后后面N位是小数部分。
但是,我们的结果需要输出π的各位数字(从3开始,然后小数部分),所以我们可以将pi_vec(1..N+1)反转,得到从高位到低位的表示(即第一个元素是10^N位,最后一个元素是个位),然后我们还需要反转这个子向量,使得第一个元素是3(10^0位),第二个元素是1(10^{-1}位),...,但我们希望输出的是[3,1,4,1,5,9,...](从整数部分到小数部分)。
实际上,pi_vec(1)是10^0位(个位,即3),pi_vec(2)是10^{-1}位(即1),pi_vec(3)是10^{-2}位(即4)...。所以,我们只需要取pi_vec(1:N+1)(即从10^0位到10^{-N}位,共N+1位),然后顺序输出:pi_vec(1)(3),pi_vec(2)(1),pi_vec(3)(4),...,pi_vec(N+1)(第N+1位)。
但是,注意:我们计算时,级数求和可能产生进位,所以pi_vec(1)(个位)可能大于9?不,不会,因为我们的向量每一位都是0-9,所以pi_vec(1)就是3(但可能进位,比如3.1...进位成4.1...,但π=3.14...,不会进位到4)。所以,我们直接取pi_vec(1)到pi_vec(N+1)即可。
但是,我们计算时,级数求和可能有进位,所以个位(pi_vec(1))可能大于9,然后进位到十位(pi_vec(2))等。所以,我们在加减法函数中已经处理了进位,所以pi_vec的每一位都是0-9。
因此,我们只需要取pi_vec(1:N+1)(即前N+1个元素),然后输出,注意:第1个元素是整数部分(3),第2个元素是小数点后第一位,...,第N+1个元素是小数点后第N位。
但是,我们要求输出一个向量,例如[3,1,4,1,5,9,2,6](对于N=8)。
所以,输出:pi_digits = pi_vec(1:N+1) % 从10^0位到10^{-N}位
但是,注意:我们计算时,级数项可能在小数点后很多位才精确,所以我们需要保护位(我们多算了10位),所以最后我们取前N+1位(即整数部分+小数部分N位),然后舍去后面的保护位。
然而,由于我们计算时使用了保护位(多算了10位),所以我们可以进行四舍五入?但这里,我们直接截断(因为保护位足够,所以第N+1位后的影响很小,不会导致第N+1位的舍入错误)。所以,我们直接取前N+1位。
因此,最后输出:pi_digits = pi_vec(1:N+1)
但是,pi_vec(1)是整数部分(3),pi_vec(2)是0.1位,...,pi_vec(N+1)是0.000...01位(第N位)。
所以,我们输出向量:pi_digits = [pi_vec(1), pi_vec(2), pi_vec(3), ..., pi_vec(N+1)]
这就是π的各位数字(从整数部分到小数部分,共N+1位)。
注意:我们要求输出的是小数点后digits位,但我们计算时N=digits+10,所以pi_vec(1:digits+1)就是我们需要的(整数部分+digits位小数),但注意:我们多算了10位,所以最后我们只取前digits+1位(即整数部分和小数部分前digits位)。
因此,最后输出:pi_digits = pi_vec(1:digits+1)
但是,我们计算时,级数求和可能在小数点后digits+10位是精确的,所以前digits+1位是准确的。
现在,我们汇总代码:
% 输入
digits = input('请输入要计算的圆周率位数: ');
% 设置保护位
guard = 10;
N = digits + guard;
% 设置高精度整数向量的总长度M(>=N+1,我们设置M=N+100)
M = N + 100;
% 初始化基数向量(10^N):一个1后面N个0,存储为从低位到高位(长度M,前面补0,最后一位是1,位置在N+1)
base_vec = zeros(1, M);
base_vec(N+1) = 1; % 注意:索引从1开始,base_vec(1)是10^0位,base_vec(2)是10^1位,...,base_vec(N+1)是10^N位
% 计算 arctan(1/5) 的级数
x = 5;
s5 = zeros(1, M); % 初始化和(全0)
sign = 1;
term_vec = divvec(base_vec, x); % 第一项:10^N / x
s5 = addvec(s5, term_vec); % 加上第一项
k = 0;
while any(term_vec) % 当term_vec不全0时继续
k = k + 1;
% 递推计算下一项
% 先乘以 (2*k-1)
term_vec = multvec(term_vec, 2*k-1);
% 再除以 x*x*(2*k+1)
% 分三次除
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, 2*k+1);
% 符号取反
sign = -sign;
% 加到s5
if sign > 0
s5 = addvec(s5, term_vec);
else
s5 = subvec(s5, term_vec);
end
end
% 计算 arctan(1/239) 的级数
x = 239;
s239 = zeros(1, M); % 初始化和
sign = 1;
term_vec = divvec(base_vec, x); % 第一项
s239 = addvec(s239, term_vec); % 加上第一项
k = 0;
while any(term_vec)
k = k + 1;
term_vec = multvec(term_vec, 2*k-1);
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, 2*k+1);
sign = -sign;
if sign > 0
s239 = addvec(s239, term_vec);
else
s239 = subvec(s239, term_vec);
end
end
% 计算 16 * s5 和 4 * s239
s5 = multvec(s5, 16); % 16 * arctan(1/5)
s239 = multvec(s239, 4); % 4 * arctan(1/239)
% 计算 pi = 16*arctan(1/5) - 4*arctan(1/239)
pi_vec = subvec(s5, s239); % pi_vec = s5 - s239
% 提取前digits+1位(整数部分+digits位小数)
pi_digits = pi_vec(1:digits+1); % 注意:pi_vec(1)是整数部分(3),pi_vec(2)是小数点后第一位,...,pi_vec(digits+1)是小数点后第digits位
% 输出 pi_digits
但是,我们还没有实现divvec, multvec, addvec, subvec。而且,代码中不能出现7和8,所以我们在写数字时,用表达式代替。
注意:在循环中,2*k+1 可能等于7,9,11,...,我们避免直接写7或8,所以当我们需要7时,写6+1,8写9-1,等等。
但是,在divvec函数中,我们有一个循环:for i=1:len,这里1不是7或8,所以没关系。
我们实现辅助函数:
function [Q, r] = divvec(A, d)
% 注意:d可能很大,但通常d<1000 (x=5,239, 2*k+1<1000)
% 反转A,得到高位到低位
A_rev = fliplr(A);
len = length(A_rev);
Q_rev = zeros(1, len); % 商的 reverse 存储
r = 0;
for i = 1:len
num = r * 10 + A_rev(i);
Q_rev(i) = floor(num / d);
r = mod(num, d);
end
Q = fliplr(Q_rev);
% 注意:Q的长度与A相同,可能高位有0
end
function B = multvec(A, m)
% 乘法
len = length(A);
carry = 0;
B = zeros(1, len);
for i = 1:len
temp = A(i) * m + carry;
carry = floor(temp / 10);
B(i) = mod(temp, 10);
end
% 处理进位
while carry > 0
B = [B, mod(carry,10)];
carry = floor(carry/10);
end
% 但我们现在B的长度可能超过len,但我们有总长度限制M,所以我们需要截断或扩展到M
% 这里,我们规定 B 的长度不能超过 M (global M)
global M
if length(B) > M
B = B(1:M);
else
% 不足M,后面补0
B = [B, zeros(1, M-length(B))];
end
end
function C = addvec(A, B)
global M
len = max(length(A), length(B));
A = [A, zeros(1, len-length(A))];
B = [B, zeros(1, len-length(B))];
C = zeros(1, len);
carry = 0;
for i = 1:len
s = A(i) + B(i) + carry;
carry = floor(s / 10);
C(i) = mod(s, 10);
end
if carry > 0
C = [C, carry];
end
% 调整到长度M
if length(C) > M
C = C(1:M);
else
C = [C, zeros(1, M-length(C))];
end
end
function C = subvec(A, B)
% 假设A>=B
global M
len = max(length(A), length(B));
A = [A, zeros(1, len-length(A))];
B = [B, zeros(1, len-length(B))];
C = zeros(1, len);
borrow = 0;
for i = 1:len
d = A(i) - borrow - B(i);
if d < 0
borrow = 1;
d = d + 10;
else
borrow = 0;
end
C(i) = d;
end
if borrow ~= 0
error('A < B!');
end
% 调整到长度M
if length(C) > M
C = C(1:M);
else
C = [C, zeros(1, M-length(C))];
end
end
注意:在函数中,我们使用global M,所以需要在主程序中声明M为全局变量。
但是,由于MATLAB中全局变量需要谨慎使用,我们可以将M作为参数传递给这些函数。但为了简单,我们这里用global。
另外,由于数字7和8不能在代码中出现,我们在写常数时,用表达式代替。例如,在divvec中,floor(num/d)等不会出现7或8的字面量,因为d是变量。只有在写固定常数时要注意,比如16,4,5,239等,这些数字不包含7或8,所以可以直接写。
但是,在循环中,2*k+1 可能等于7,我们写 2*k+1 本身,因为k是变量,所以不会出现7的字面量。在divvec(term_vec, 2*k+1)中,2*k+1是一个表达式,不会出现7的字面量。所以,我们不需要额外处理。
但是,在递推公式中,我们写 2*k-1 和 2*k+1,都是表达式,所以安全。
最后,我们输出 pi_digits,但注意:pi_digits(1)是整数部分(3),pi_digits(2)是小数第一位,...,pi_digits(digits+1)是小数第digits位。
所以,我们可以直接显示:
disp(['π = ', num2str(pi_digits(1)), '.', num2str(pi_digits(2:end))]);
但题目要求输出向量,所以直接输出 pi_digits 即可。
注意:由于我们使用了 global M,需要在函数中声明,而且可能速度较慢,但 for 100位小数,循环次数不会太多(级数项衰减很快),所以可以接受。
但是,我们计算 arctan(1/239) 的级数收敛得比 arctan(1/5) 快,所以循环次数少。
由于代码较长,且涉及高精度计算,可能在实际运行中出现问题(比如向量长度变化),我们可以用较小的 digits 测试(比如10位)。
由于时间限制,我们给出完整代码,并尽量测试。
注意:代码中不能出现7或8,但我们检查后,没有直接出现7或8的字面量(16,4,5,239,1,2,3,6,9都不是7或8,2*k+1是表达式,不会出现7或8的字面量)。
现在,给出完整代码:
(注意:由于MATLAB中向量操作,我们尽量向量化,但这里循环是必要的)
由于代码较长,我们将其写在一个函数中:
function pi_digits = calculate_pi(digits)
% 设置保护位
guard = 10;
N = digits + guard;
% 设置高精度整数向量的总长度M
M = N + 100;
global M % 全局变量,用于辅助函数
% 初始化 base_vec = 10^N (1 followed by N zeros), stored as a vector (least significant first)
base_vec = zeros(1, M);
base_vec(N+1) = 1; % base_vec(i) corresponds to 10^(i-1)
% 计算 arctan(1/5)
x = 5;
s5 = zeros(1, M); % initialize sum to zero
sign = 1;
term_vec = divvec(base_vec, x); % first term: 10^N / x
s5 = addvec(s5, term_vec); % add first term
k = 0;
while any(term_vec) % continue until the term becomes zero
k = k + 1;
% Multiply by (2*k-1)
term_vec = multvec(term_vec, 2*k-1);
% Divide by x*x*(2*k+1)
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, 2*k+1); % note: 2*k+1 may be 7,9, etc., but it's an expression.
sign = -sign; % flip the sign
if sign > 0
s5 = addvec(s5, term_vec);
else
s5 = subvec(s5, term_vec);
end
end
% 计算 arctan(1/239)
x = 239;
s239 = zeros(1, M);
sign = 1;
term_vec = divvec(base_vec, x);
s239 = addvec(s239, term_vec);
k = 0;
while any(term_vec)
k = k + 1;
term_vec = multvec(term_vec, 2*k-1);
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, x);
term_vec = divvec(term_vec, 2*k+1);
sign = -sign;
if sign > 0
s239 = addvec(s239, term_vec);
else
s239 = subvec(s239, term_vec);
end
end
% Multiply s5 by 16 and s239 by 4
s5 = multvec(s5, 16);
s239 = multvec(s239, 4);
% pi = 16*arctan(1/5) - 4*arctan(1/239)
pi_vec = subvec(s5, s239);
% Extract the first (digits+1) digits (from 10^0 to 10^{-digits})
pi_digits = pi_vec(1:digits+1);
end
% 辅助函数
function [Q, r] = divvec(A, d)
% A: vector from least significant to most significant digit (A(1) is units place)
% d: integer divisor
% Q: quotient in the same storage order
% r: remainder
global M
% Flip to get from most significant to least significant
A_rev = fliplr(A);
len = length(A_rev);
Q_rev = zeros(1, len);
r = 0;
for i = 1:len
num = r * 10 + A_rev(i);
Q_rev(i) = floor(num / d);
r = mod(num, d);
end
Q = fliplr(Q_rev);
% Adjust length to M
if length(Q) > M
Q = Q(1:M);
else
Q = [Q, zeros(1, M-length(Q))];
end
end
function B = multvec(A, m)
% A: vector (least significant first)
% m: integer multiplier
global M
len = length(A);
carry = 0;
B = zeros(1, len);
for i = 1:len
temp = A(i) * m + carry;
carry = floor(temp / 10);
B(i) = mod(temp, 10);
end
while carry > 0
B = [B, mod(carry,10)];
carry = floor(carry / 10);
end
% Adjust to length M
if length(B) > M
B = B(1:M);
else
B = [B, zeros(1, M-length(B))];
end
end
function C = addvec(A, B)
% A, B: vectors (least significant first)
global M
len = max(length(A), length(B));
A = [A, zeros(1, len-length(A))];
B = [B, zeros(1, len-length(B))];
C = zeros(1, len);
carry = 0;
for i = 1:len
s = A(i) + B(i) + carry;
carry = floor(s / 10);
C(i) = mod(s, 10);
end
if carry > 0
C = [C, carry];
end
if length(C) > M
C = C(1:M);
else
C = [C, zeros(1, M-length(C))];
end
end
function C = subvec(A, B)
% A, B: vectors (least significant first), and A>=B
global M
len = max(length(A), length(B));
A = [A, zeros(1, len-length(A))];
B = [B, zeros(1, len-length(B))];
C = zeros(1, len);
borrow = 0;
for i = 1:len
d = A(i) - borrow - B(i);
if d < 0
borrow = 1;
d = d + 10;
else
borrow = 0;
end
C(i) = d;
end
if borrow ~= 0
error('A < B in subtraction');
end
if length(C) > M
C = C(1:M);
else
C = [C, zeros(1, M-length(C))];
end
end
注意:由于我们使用了global M,在函数中需要声明,而且必须在 calculate_pi 函数中定义 global M,然后在辅助函数中也声明 global M。
但是,MATLAB中,global变量必须在每个函数中声明。
由于代码较长,且高精度计算效率不高,对于100位小数,我们可能需要较长的运行时间。
我们可以用tic/toc测试。
另外,注意:在 while any(term_vec) 循环中,any(term_vec) 可能会判断向量中是否有非零元,但随着项变小, term_vec 会变成0向量,所以会终止。
最后,我们输出 pi_digits,例如:
digits = 8;
pi_digits = calculate_pi(digits);
disp(pi_digits); % should be [3,1,4,1,5,9,2,6,5,3] (if digits=8, then output has 9 elements: integer part and 8 decimals)
注意:输出向量长度 digits+1,第一个是3,后面是小数部分。
由于时间原因,我们可能无法详细测试,但你可以用较小的 digits 测试。
相关问题:
§§相关问题§§:
1. 马青公式还有哪些其他形式?
2. 除了马青公式,还有哪些算法可以计算圆周率的高精度值?
3. 在高精度计算中,如何优化除法运算以提高效率?
4. 如何验证计算出的小数位的正确性?
5. 如果要求计算圆周率的1000位小数,如何改进代码以提高速度?
6. 除了十进制,如何用其他进制(如二进制)计算圆周率的高精度值?
7. 在高精度计算中,如何处理舍入误差?
8. 泰勒级数展开的收敛速度如何影响计算效率?
9. 是否有比泰勒级数更快的级数方法来计算反正切函数?
10. 如何用并行计算加速圆周率的计算?</think>要使用马青公式计算圆周率的指定位数小数,可以通过高精度整数运算实现。马青公式为:
\[
\pi = 16 \cdot \arctan\left(\frac{1}{5}\right) - 4 \cdot \arctan\left(\frac{1}{239}\right)
\]
其中,\(\arctan\) 函数使用泰勒级数展开:
\[
\arctan(x) = x - \frac{x^3}{3} + \frac{x^5}{5} - \frac{x^7}{7} + \cdots
\]
为获得高精度结果,使用整数向量表示大数(每位十进制数字作为向量元素),并实现高精度除法、乘法和加减法。代码中避免直接使用数字 7 或 8(用表达式如 `6+1` 替代)。
### 解决方法
1. **初始化参数**:
- 输入所需位数 `digits`,添加保护位(如 10 位)避免舍入误差。
- 设置向量长度 `M = digits + 110` 确保足够存储。
- 创建基数向量 `base_vec` 表示 \(10^{digits + \text{guard}}\)。
2. **计算 \(\arctan(1/5)\) 和 \(\arctan(1/239)\)**:
- 使用泰勒级数,逐项计算并累加(每项通过高精度除法、乘法和符号处理)。
- 递推关系:下一项 = 当前项 \(\times \frac{2k-1}{(2k+1) \cdot x^2}\),其中 \(x = 5\) 或 \(239\)。
3. **组合结果**:
- 计算 \(16 \cdot \arctan(1/5)\) 和 \(4 \cdot \arctan(1/239)\)。
- 相减得到 \(\pi\) 的整数向量表示。
4. **提取结果**:
- 取前 `digits + 1` 位(整数部分 + 小数部分)。
### MATLAB 代码
```matlab
function pi_digits = calculate_pi(digits)
% 设置保护位和向量长度
guard = 10;
N = digits + guard;
M = N + 100; % 总向量长度
global M; % 全局变量用于辅助函数
% 初始化基数向量 (10^N)
base_vec = zeros(1, M);
base_vec(N+1) = 1; % 10^N 对应位置
% 计算 arctan(1/5)
x5 = 5;
s5 = zeros(1, M);
sign_val = 1;
term_vec = divvec(base_vec, x5);
s5 = addvec(s5, term_vec);
k = 0;
while any(term_vec)
k = k + 1;
term_vec = multvec(term_vec, 2*k - 1);
term_vec = divvec(term_vec, x5);
term_vec = divvec(term_vec, x5);
term_vec = divvec(term_vec, 2*k + 1); % 避免 7/8 字面量
sign_val = -sign_val;
if sign_val > 0
s5 = addvec(s5, term_vec);
else
s5 = subvec(s5, term_vec);
end
end
% 计算 arctan(1/239)
x239 = 239;
s239 = zeros(1, M);
sign_val = 1;
term_vec = divvec(base_vec, x239);
s239 = addvec(s239, term_vec);
k = 0;
while any(term_vec)
k = k + 1;
term_vec = multvec(term_vec, 2*k - 1);
term_vec = divvec(term_vec, x239);
term_vec = divvec(term_vec, x239);
term_vec = divvec(term_vec, 2*k + 1); % 避免 7/8 字面量
sign_val = -sign_val;
if sign_val > 0
s239 = addvec(s239, term_vec);
else
s239 = subvec(s239, term_vec);
end
end
% 应用马青公式系数
s5 = multvec(s5, 16);
s239 = multvec(s239, 4);
pi_vec = subvec(s5, s239);
% 提取结果 (整数部分 + digits 位小数)
pi_digits = pi_vec(1:digits+1);
end
% 高精度除法 (向量除以整数)
function [Q, r] = divvec(A, d)
global M;
A_rev = fliplr(A);
len = length(A_rev);
Q_rev = zeros(1, len);
r = 0;
for i = 1:len
num_val = r * 10 + A_rev(i);
Q_rev(i) = floor(num_val / d);
r = mod(num_val, d);
end
Q = fliplr(Q_rev);
if length(Q) > M
Q = Q(1:M);
else
Q = [Q, zeros(1, M - length(Q))];
end
end
% 高精度乘法 (向量乘以整数)
function B = multvec(A, m)
global M;
len = length(A);
carry = 0;
B = zeros(1, len);
for i = 1:len
temp_val = A(i) * m + carry;
carry = floor(temp_val / 10);
B(i) = mod(temp_val, 10);
end
while carry > 0
B = [B, mod(carry, 10)];
carry = floor(carry / 10);
end
if length(B) > M
B = B(1:M);
else
B = [B, zeros(1, M - length(B))];
end
end
% 高精度加法
function C = addvec(A, B)
global M;
len = max(length(A), length(B));
A = [A, zeros(1, len - length(A))];
B = [B, zeros(1, len - length(B))];
C = zeros(1, len);
carry = 0;
for i = 1:len
s = A(i) + B(i) + carry;
carry = floor(s / 10);
C(i) = mod(s, 10);
end
if carry > 0
C = [C, carry];
end
if length(C) > M
C = C(1:M);
else
C = [C, zeros(1, M - length(C))];
end
end
% 高精度减法 (假设 A >= B)
function C = subvec(A, B)
global M;
len = max(length(A), length(B));
A = [A, zeros(1, len - length(A))];
B = [B, zeros(1, len - length(B))];
C = zeros(1, len);
borrow = 0;
for i = 1:len
d = A(i) - borrow - B(i);
if d < 0
borrow = 1;
d = d + 10;
else
borrow = 0;
end
C(i) = d;
end
if borrow ~= 0
error('A < B in subtraction');
end
if length(C) > M
C = C(1:M);
else
C = [C, zeros(1, M - length(C))];
end
end
```
### 使用示例
```matlab
% 计算圆周率前 100 位小数
digits = 100;
pi_digits = calculate_pi(digits);
% 显示结果(整数部分 + 小数部分)
disp(['π = ', num2str(pi_digits(1)), '.', num2str(pi_digits(2:end))]);
```
### 解释
1. **初始化保护位**:多算 10 位避免舍入误差影响最终精度。
2. **向量存储**:大整数用向量表示(从低位到高位),如 \(10^3\) 存储为 `[0, 0, 0, 1]`。
3. **泰勒级数**:逐项计算 \(\arctan\),递推减小计算量。
4. **高精度运算**:
- **除法**:向量反转后模拟手算除法。
- **乘法**:逐位相乘并处理进位。
- **加减法**:逐位运算并处理进位/借位。
5. **避免数字 7/8**:用表达式(如 `2*k+1`)替代字面量,确保代码合规。
### 相关问题