一种小数转分数的算法(不限整除)C++
来源:互联网 发布:软件测试 静态测试 编辑:程序博客网 时间:2024/06/05 19:52
最近需要用到小数转分数算法,便研究了一下。
先看一下最终程序的效果:
说一下数学中有理小数转分数的过程:
有理小数分为有限小数和无限循环小数
1. 有限小数:
有限小数直接去小数点再约分即可。
例:
2. 无限循环小数:
先判断循环节长度
例:
设
循环节为123,一共三位,乘以
两式相减可得
刚开始想使用上述方法,于是做了一些实验。
计算器算了分数,123/321=0.38317757009345794392523364485981,可见,123、321这两个数并不是很大,但是相除后得到的小数循环节过长,一个double类型尾数长度是52bit,对应十进制只有15~16位的精度,不能保证用算法分析出循环节的长度,上述方法不适用。下面使用连分数做转换。
使用连分数做转换
一、连分数的定义:
形如
有理数(整数、有限小数、无限循环小数)的连分式是有限的。
二、有理数与连分数的转换:
1. 分数或小数转换为连分数
步骤1:令a=这个数,即a←这个数;
步骤2:将a的整数部分⌊a⌋记录下来;
步骤3:令a为a的小数部分,即a←a-⌊a⌋;
步骤4:如果a≠0,跳转到步骤2,否则向下执行步骤5;
步骤5:步骤2中记录的数即为连分数的
例如将
可见
2. 连分数转换为分数或小数
直接通分即可:如
3. 利用上述方法,将有理数转为连分数再通分,可将2.6097560975... 转为分数10741 。
三、转换算法的C++程序
class ContinuedFraction{private: //用于保存连分数列表 std::vector<unsigned> nums;public: //小数转为连分数的构造函数 ContinuedFraction(double a); //连分数转为小数 double ToDouble();};ContinuedFraction::ContinuedFraction(double a){ if (a < 1e-12) { nums.push_back(0); return; } for (;;) { unsigned tr = (unsigned)a; nums.push_back(tr); a -= tr; if (a < 1e-12) //小数部分是0, 退出 { return; } a = 1 / a; }}double ContinuedFraction::ToDouble(){ double a = nums.back(); for (auto it = nums.rbegin() + 1; it != nums.rend(); it++) { a = 1 / a; a += *it; } return a;}
四、计算机转换时的问题
上述转换算法,我们需要做步骤3中的判断(a≠0)才能知道什么时候跳出循环,由于浮点型的误差,程序中a与0作比较应该这样写:abs(a - 0) < 1e-12
,其中1e-12为很小的数,也就是说浮点型a与b比较相等,由于误差,需要写成a与b的差的绝对值是否充分小。
程序中的unsigned tr = (unsigned)a; a -= tr;
功能是取a中的小数部分,初始浮点型为15位的有效数字,但减去整数部分后会引起浮点型的误差增大。
例如123.00123(8个有效数字)取小数后为0.00123(3个有效数字),有效数字的个数减少了5。
也就是说有效数字减少的个数为 整数的总位数3(123为3位)+小数点后零的个数2(0.00123有2个0)=5。
由于循环的次数,误差将越来越大,所以代码中应该计算实时的误差为多少,才能使a≠0的判断更有效。
程序如下
//初始浮点型的误差double esp = 1e-15;for (;;){ unsigned tr = (unsigned)a; nums.push_back(tr); a -= tr; if (a < 1e-12) //小数部分是0, 直接退出 { return; } //因a减整数部分而引起的浮点型误差增大 //例如123.123,变为0.123,精度也由6位变为3位 while (tr > 0) { tr /= 10; esp *= 10; } //因a取小数引起的浮点型误差增大 //例如123.00123,变为0.00123,精度也由8位变为3位 double t = a; while (t < 0.1) { t *= 10; esp *= 10; } a = 1 / a;}
上面代码已经实时的计算出浮点数的误差esp,理论上在最后一句a = 1 / a;
前面加上if (a < esp) return;
即可。但是我们计算浮点数的误差过于严谨,可能导致还没有到0就提前退出循环了。所有这里我们把a < esp时候的插入nums的索引indexZheng记录下来,向后再多计算几次一直到2*indexZheng后再退出循环。
转换为分数时,可以分别将nums个数取indexZheng到2*indexZheng对应的double值与给定的double值比较,取误差最小对应的分数。
最终的程序如下:
ContinuedFraction.h
#pragma once#include <vector>class ContinuedFraction{private: //用于保存连分数列表 std::vector<unsigned> nums; int indexZheng = -1;public: //小数转为连分数的构造函数 ContinuedFraction(double a); //连分数转为小数 double ToDouble(); friend class Fraction;};
ContinuedFraction.cpp
#include "ContinuedFraction.h"ContinuedFraction::ContinuedFraction(double a){ if (a < 1e-12) { nums.push_back(0); return; } //初始浮点型的误差,经测试1e-12效果最好 double esp = 1e-12; for (int i = 0; indexZheng == -1 || i <= 2 * (indexZheng + 5); i++) { unsigned tr = (unsigned)a; nums.push_back(tr); a -= tr; if (a < 1e-12) //小数部分是0, 直接退出 { if (indexZheng == -1) { indexZheng = i; } return; } //因a减整数部分而引起的浮点型误差增大 //例如123.123,变为0.123,精度也由6位变为3位 while (tr > 0) { tr /= 10; esp *= 10; } ////因a取小数引起的浮点型误差增大 ////例如123.00123,变为0.00123,精度也由8位变为3位 //double t = a; //while (t < 0.1) //{ // t *= 10; // esp *= 10; //} if (indexZheng == -1 && a < esp) { indexZheng = i; } a = 1 / a; }}double ContinuedFraction::ToDouble(){ double a = nums.back(); for (auto it = nums.rbegin() + 1; it != nums.rend(); it++) { a = 1 / a; a += *it; } return a;}
Fraction.h
#pragma once#include "ContinuedFraction.h"class Fraction{private: //符号位 bool sign; //倒数 void Invert();public: //分子 unsigned a; //分母 unsigned b; Fraction() {} Fraction(double a); Fraction(ContinuedFraction confrac); void Print(); double ToDouble();};
Fraction.cpp
#include "Fraction.h"#include <cmath>#include <vector>#include <iostream>Fraction::Fraction(ContinuedFraction confrac) : b(1), sign(false){ a = confrac.nums.back(); for (auto it = confrac.nums.rbegin() + 1; it != confrac.nums.rend(); it++) { Invert(); a += b * *it; }}Fraction::Fraction(double a){ sign = a < 0; if (sign) a = -a; if (a < 1e-12) { this->a = 0; this->b = 1; this->sign = false; return; } ContinuedFraction confrac(a); std::vector<unsigned> nums = confrac.nums; int firstIndex = confrac.indexZheng - 4; firstIndex = firstIndex >= 0 ? firstIndex : 0; confrac.nums.resize(firstIndex); std::vector<Fraction> vfr; std::vector<double> vesp; //一次一次增加连分数精度,并计算误差 for (size_t i = firstIndex; i < nums.size(); i++) { confrac.nums.push_back(nums[i]); Fraction fr(confrac); double d = fr.ToDouble(); vfr.push_back(fr); vesp.push_back(abs(d - a)); } //查找误差最小的index int mindex = 0; double m = 1e100; for (size_t i = 0; i < vesp.size(); i++) { if (vesp[i] < m) { mindex = i; m = vesp[i]; } } this->a = vfr[mindex].a; this->b = vfr[mindex].b; return;}void Fraction::Invert(){ unsigned t = a; a = b; b = t;}void Fraction::Print(){ std::cout << (sign ? "-" : "") << a << "/" << b << std::endl;}double Fraction::ToDouble(){ double n = (double)a / b; return sign ? -n : n;}
main.cpp
#include "Fraction.h"int main(){ Fraction f1(46531463. / 34554517); f1.Print(); Fraction f2(4.25); f2.Print(); Fraction f3(-1.3333333333333); f3.Print(); return 0;}
- 一种小数转分数的算法(不限整除)C++
- 分数转小数(模拟)
- 分数转小数(模拟)
- [C++]小数转化为分数(class)
- scanf的一一对应,分数转小数。
- 小数、分数的计算
- 算法:用小数表示分数
- 很有用的小数化为分数算法!小学生的~
- SQL 分数转小数
- 【OJ】分数转小数
- 小数转分数
- HDACM218 小数转分数
- 小数转分数
- 小数转分数
- C语言 分数化小数
- 算法竞赛入门经典(分数化小数)
- 分数与小数的转换
- 找最接近小数的分数
- Java泛型详解
- Android试题
- ImputStream源码学习
- Windows19
- MATLAB在密码学中的应用
- 一种小数转分数的算法(不限整除)C++
- java面试题
- start bat并行执行dos exe指令
- Scroller解析
- ubuntu实时监视GPU情况
- C语言的数组
- 古人与古代(他们和他们的时代)
- 【剑指offer】面试题 11:旋转数组的最小数字
- LeetCode#7 Reverse Integer