求黄金分割数的小数点后100位(蓝桥杯竞赛题)

六月ゝ 毕业季﹏ 提交于 2020-03-17 07:58:47

网上虽然有很多解答,但不一定都对,这个是肯定对的。

本题是蓝桥杯的省赛题,题目原文如下

黄金分割数0.61803… 是个无理数,这个常数十分重要,在许多工程问题中会出现。有时需要把这个数字求得很精确。
对于某些精密工程,常数的精度很重要。也许你听说过哈勃太空望远镜,它首次升空后就发现了一处人工加工错误,对那样一个庞然大物,其实只是镜面加工时有比头发丝还细许多倍的一处错误而已,却使它成了“近视眼”!!
言归正传,我们如何求得黄金分割数的尽可能精确的值呢?有许多方法。
比较简单的一种是用连分数:f(n)=1/(1+f(n-1)) abs(f(n)-f(n-1))<10e-100
(连分数的图见QQ群)
这个连分数计算的“层数”越多,它的值越接近黄金分割数。
请你利用这一特性,求出黄金分割数的足够精确值,要求四舍五入到小数点后100位。
小数点后3位的值为:0.618
小数点后4位的值为:0.6180
小数点后5位的值为:0.61803
小数点后7位的值为:0.6180340
(注意尾部的0,不能忽略)
你的任务是:写出精确到小数点后100位精度的黄金分割值。
注意:尾数的四舍五入! 尾数是0也要保留!
显然答案是一个小数,其小数点后有100位数字。
注意:不要提交解答过程,或其它辅助说明类的内容。

说明一些函数的功能:

//find_first_not_of(‘0’); //顾名思义,找到第一个不是0字符的位置
//找到第一个不是0的位置之后,将这个位置后面的字符串拿走,因为这才是真正需要的

a = a.substr(find_first_not_of('0'));

作用:将一个字符串反转

reverse(a.begin(), a.end());

将一个字符串 0 加入到string a 的末尾

a.append("0");

这里有点像NULL,具体的含义作用稍后说明

string::npos

下面是函数功能的说明

1. 加法函数

string add(string a,string b){
	a = a.substr(a.find_first_not_of('0'));
	b = b.substr(b.find_first_not_of('0'));
	
	long long lenA = a.length();
	long long lenB = b.length();
	long long len = max(lenA,lenB) + 10;
//翻转,便于从低位逐步求和
	reverse(a.begin(), a.end());	 
	reverse(b.begin(), b.end());
	
	string ans(len,'0');//将答案初始化为00000000...
	
	//core code core code core code core code core code
	//将a拷贝到ans中
	for(int i = 0; i < lenA; i++){
		ans[i] = a[i];
	}
	
	//开始执行加法了 
	int temp = 0;//temp 是上一位相加的进位 
	for(int i = 0; i < len; ++i){
		if(i < b.length())
			temp += (ans[i] - '0') + (b[i] - '0');	//一位的加法
		else
			temp += (ans[i] - '0');
		ans[i] = temp % 10 + '0';	//使数字又变回字符形式 
		temp /= 10; 
	}
	//相加完之后翻转
	reverse(ans.begin(),ans.end()); 
	 
	return ans.substr(ans.find_first_not_of('0'));
	// core code core code core code core code core code core code core code
}

对这些代码进行说明,

首先,我们要做的是,让这些输入进来的数字变得美观,合适,方便我们运算
比如001,其实就是1,但二者其实都是合理合法的,只是前者不方便我们进行
加法运算而已,所以去掉前缀0,前缀(prefix),就变成了我们平常用的加法

	a = a.substr(a.find_first_not_of('0'));
	b = b.substr(b.find_first_not_of('0'));

其次,测量两个string 数的长度,翻译到数学上其实就是位数,我们确保两个数相加之后的len,肯定是lenA,lenB二者最大再加10位,其实这里多加了,是为了确保编程的方便,比如99+99 = 198 ,也就变成了3位,999+9999=10998,也就从4位变成了5位,依次类推,最多增加一个长度。
然后这些数字都是从小到大输入进来的,98754,而我们数组的储存是从低到高存储的,比如a[0] = 1,a[1] = 8, a[2] = 7…,这样不方便我们遍历。
因为我们要从个位数开始加起,我们平常的计算也是这样算的,所以再次反转,用不用担心呢,不用,我们算完,又会反转回来。而ans其实就是结果string,先初始化为0,

	long long lenA = a.length();
	long long lenB = b.length();
	long long len = max(lenA,lenB) + 10;
//翻转,便于从低位逐步求和
	reverse(a.begin(), a.end());	 
	reverse(b.begin(), b.end());

	string ans(len,'0');//将答案初始化为00000000...

首先,将a[]数组,拷贝到答案中,其实可以直接将a作为父体,因为我们手算是这样算的,但这样无法通过DevC++的编译,因为这里是值传递,而不是地址传递,所以拷贝一份吧
先定义一个temp,这个temp其实从始至终只是储存1位数字,最多2位,就是当下ans[i]和b[i]相加,所产生的数,如果是1位,就留着,2位,就要进位了
if(i < b.length())确保我们无论是把长度短的那个先加完,还是长度长的那个先加完,都是正确的,比如,123+12345,我们先把123(b)加完了,就会执行else语句,如果是12345(b)的话,那一直在if(i < b.length())中

ans[i] = temp % 10 + ‘0’ 的作用是,1位保留,2位就留个位,比如9%10 = 9,19%10 = 9
temp /= 10 这里的作用就是留下十位,用来准备下一次的进位

//将a拷贝到ans中
	for(int i = 0; i < lenA; i++){
		ans[i] = a[i];
	}
	
	//开始执行加法了 
	int temp = 0;//temp 是上一位相加的进位 
	for(int i = 0; i < len; ++i){
		if(i < b.length())
			temp += (ans[i] - '0') + (b[i] - '0');	//一位的加法
		else
			temp += (ans[i] - '0');
		ans[i] = temp % 10 + '0';	//使数字又变回字符形式 
		temp /= 10; 
	}

对加法函数进行一个总结,就是将我们的竖式加法手工转化成代码,其实反映到现实中,计算机就是对我们的人工过程进行了虚拟重构,我们如果说现实中能够实现的,计算机绝大多数也能够实行,只要它是有穷的,可逻辑化,形式化的。

2.减法函数

//此处默认,a一定大于b ,a - b,
string subtract(string a,string b){
//完整的减法中,a 可以小于b,这时结果为负数,交换ab即可
 	//1.翻转 
	reverse(a.begin(), a.end());
 	reverse(b.begin(), b.end());
 	//2.按位减法
	//拷贝a到ans中
	//string ans = a;
	//这里写成了a.length()的话会出大麻烦 
	for(int i = 0; i < b.length(); i++){
		if(a[i] >= b[i]){
			a[i] = a[i] - b[i] + '0';//这一位上a>b 
		}else{						  //这一位小了,要借位 
			int next = 1;
			while(a[i + next] == '0') {
			a[i+next] = '9';
			next++;
			}
			//这里保证i + k这一位不是0,因为已经跳出了while循环
			a[i+next] = a[i+next] - '1' + '0';	//重点语句,必须加上'0' ,这是借位 
			a[i] = (a[i]-'0'+10) - (b[i]-'0')+ '0';	//10是借位过来的,最后的+'0'使得重新成为了string 
		}
	}
	reverse(a.begin(), a.end());
	if(a.find_first_not_of('0') == string::npos)	return "0";//ans.find_first_not_of('0')有个特殊规则,本身是0的话,总不能把自己去掉 
	return a.substr(a.find_first_not_of('0'));
	
}

有些语句作用和加法一样,这里不再赘述

让我们回顾下,减法的过程,10000 - 9,第一个个位0,到第四个千位0,其实一直都是不够减的,所以一直都在借位,而且这个借位出现了传递性,明确了这个借位可以传递的思想,我们看代码

	for(int i = 0; i < b.length(); i++){
		if(a[i] >= b[i]){
			a[i] = a[i] - b[i] + '0';//这一位上a>b 
		}else{						  //这一位小了,要借位 
			int next = 1;
			while(a[i + next] == '0') {
			a[i+next] = '9';
			next++;
			}
			//这里保证i + k这一位不是0,因为已经跳出了while循环
			a[i+next] = a[i+next] - '1' + '0';	//重点语句,必须加上'0' ,这是借位 
			a[i] = (a[i]-'0'+10) - (b[i]-'0')+ '0';	//10是借位过来的,最后的+'0'使得重新成为了string 
		}
	}

由于默认,b更小,所以遍历以b的长度为准,第一个if就是大白话

for(int i = 0; i < b.length(); i++){
		if(a[i] >= b[i]){
			a[i] = a[i] - b[i] + '0';//这一位上a>b 
		}

定义了一个next,即要向多少 更大位数,进行借位,如果下一位不是0,那自然可以借到位,比如 11 - 9,但如果下一位是0,自然要再借,比如201 - 9,
随后,就是记录借位操作了,
值得注意最后一行,a[i] = (a[i]-‘0’+10) - (b[i]-‘0’)+ ‘0’ 其中的10,其实就是从下一位借位过来的1,但权值更大的1.

else{						  //这一位小了,要借位 
			int next = 1;
			while(a[i + next] == '0') {
			a[i+next] = '9';
			next++;
			}
			//这里保证i + k这一位不是0,因为已经跳出了while循环
			a[i+next] = a[i+next] - '1' + '0';	//重点语句,必须加上'0' ,这是借位 
			a[i] = (a[i]-'0'+10) - (b[i]-'0')+ '0';	//10是借位过来的,最后的+'0'使得重新成为了string 
		}
	}

讲解下这个的作用,比如,9 - 9 = 0了,没错,我就要返回0,就是返回0,但是这明显和find_first_not_of(‘0’)的作用不相符,其实find_first_not_of本身会有这个机制判断,判断本身是不是要not_of的对象,如果是的话肯定会有错,那就返回空位置咯,nullposition简写不就是npos吗?

if(a.find_first_not_of('0') == string::npos)	

3.除法函数


int cmp(string a, string b){
	
	if(a.find_first_not_of('0') == string::npos)	a = '0';
		else a.substr(a.find_first_not_of('0'));
	if(b.find_first_not_of('0') == string::npos)	b = '0';
		else b.substr(b.find_first_not_of('0'));
	
	if(a.length() > b.length())	return 1;
	else if(a.length() <  b.length())	return -1;
	else{
		if(a > b)	return 1;
		if(a < b)	return -1;
		else return 0;
	}
}

void i2s(int n, string &str){
	stringstream stream;
	stream <<n;
	stream>>str;
}
//除法的本质是减法 ,默认a < b
string divide(string a,string b){
	string ans = "0.";
	for(int i = 0; i < 101; i++){
		a.append("0");
		int t = 0;
		while(cmp(a,b) >= 0){	//a在append("0")之后 >= b 比如 1 / 2就是 (10 / 2) / 10, 10 / 2这时就变成减法 
			a = subtract(a,b);
			t++;
		}
		string t_str;		
		i2s(t, t_str);			//t是int,t_str是t的字符串形式 
		ans.append(t_str);
	}
	return ans;
}

首先,我们想想我们的除法是怎么计算的,5 / 2 = 2.5.对吧,其实也就是 50 / 2 = 25 (50够2减25次)再缩小10倍,就是先放大,再缩小,
那么a.append(“0”);就是在执行这个放大过程。

还有一个要分析的就是,这个连分数是和斐波那契数列有关的,这个大家手动的写一下就能够很快的推导出,或者参考下其它人的博客。

下面放出所有的代码


#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
using namespace std;

string add(string a,string b);
string divide(string a,string b);
string subtract(string a,string b);
void i2s(int n, string &str);
int cmp(string a, string b);
//a是分子,b是分母 

void i2s(int n, string &str){
	stringstream stream;
	stream <<n;
	stream>>str;
}

int cmp(string a, string b){
	
	if(a.find_first_not_of('0') == string::npos)	a = '0';
		else a.substr(a.find_first_not_of('0'));
	if(b.find_first_not_of('0') == string::npos)	b = '0';
		else b.substr(b.find_first_not_of('0'));
	
	if(a.length() > b.length())	return 1;
	else if(a.length() <  b.length())	return -1;
	else{
		if(a > b)	return 1;
		if(a < b)	return -1;
		else return 0;
	}
}

string add(string a,string b){
	a = a.substr(a.find_first_not_of('0'));
	b = b.substr(b.find_first_not_of('0'));
	
	long long lenA = a.length();
	long long lenB = b.length();
	long long len = max(lenA,lenB) + 10;
//翻转,便于从低位逐步求和
	reverse(a.begin(), a.end());	 
	reverse(b.begin(), b.end());
	
	string ans(len,'0');//将答案初始化为00000000...
	
	//core code core code core code core code core code
	//将a拷贝到ans中
	
	for(int i = 0; i < lenA; i++){
		ans[i] = a[i];
	}
	
	//开始执行加法了 
	int temp = 0;//temp 是上一位相加的进位 
	for(int i = 0; i < len; ++i){
		if(i < b.length())
			temp += (ans[i] - '0') + (b[i] - '0');	//一位的加法
		else
			temp += (ans[i] - '0');
		ans[i] = temp % 10 + '0';	//使数字又变回字符形式 
		temp /= 10; 
	}
	//相加完之后翻转
	reverse(ans.begin(),ans.end());  
		
	return ans.substr(ans.find_first_not_of('0'));
	// core code core code core code core code core code core code core code
}

//除法的本质是减法 ,a < b
string divide(string a,string b){
	string ans = "0.";
	for(int i = 0; i < 101; i++){
		a.append("0");
		int t = 0;
		while(cmp(a,b) >= 0){	//a在append("0")之后 >= b 比如 1 / 2就是 (10 / 2) / 10, 10 / 2这时就变成减法 
			a = subtract(a,b);
			t++;
		}
		string t_str;		
		i2s(t, t_str);			//t是int,t_str是t的字符串形式 
		ans.append(t_str);
	}
	return ans;
}

//此处,a一定大于b ,a - b
string subtract(string a,string b){
//完整的减法中,a 可以小于b,这时结果为负数,交换ab即可
 	//1.翻转 
	reverse(a.begin(), a.end());
 	reverse(b.begin(), b.end());
 	//2.按位减法
	//拷贝a到ans中
	//string ans = a;
	//这里写成了a.length()的话会出大麻烦 
	for(int i = 0; i < b.length(); i++){
		if(a[i] >= b[i]){
			a[i] = a[i] - b[i] + '0';//这一位上a>b 
		}else{						  //这一位小了,要借位 
			int next = 1;
			while(a[i + next] == '0') {
			a[i+next] = '9';
			next++;
			}
			//这里保证i + k这一位不是0,因为已经跳出了while循环
			a[i+next] = a[i+next] - '1' + '0';	//重点语句,必须加上'0' ,这是借位 
			a[i] = (a[i]-'0'+10) - (b[i]-'0')+ '0';	//10是借位过来的,最后的+'0'使得重新成为了string 
		}
	}
	reverse(a.begin(), a.end());
	if(a.find_first_not_of('0') == string::npos)	return "0";//ans.find_first_not_of('0')有个特殊规则,本身是0的话,总不能把自己去掉 
	return a.substr(a.find_first_not_of('0'));
	
}


int n = 1000;

int main(){
	string a = "1";
	string b = "1";		//4 - 4 = 0;  find_first_not_of('0') 会出错 
	string tmp;
	
	//字符串模拟裴波那契 
	for(int i = 3; i < n; i++){
		tmp = b;
		b = add(a,b);
		a = tmp;		//这是递推式 
	}	//a,b是斐波那契的n - 1和 n项 
	
	string ans = divide(a,b);
	cout<<ans<<endl;
	cout<<ans.length() - 2<<endl;	//0.占了2位 
	
	return 0;		
}

0.61803398874989484820458683436563811772030917980576286213544862270526046281890244970720720418939113748
but这是有101位的,没有考虑四舍五入的,考虑四舍五入之后,末尾8要进位,
0.6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911375
才是正确答案。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!