任意精度的小数运算
问题的大致内容如下
给定正整数 n , m , k ( n < m ) n,m,k(n<m) n,m,k(n<m),求 n / m n/m n/m 的值,保留 k k k 位小数。
这道题的我分为三步解决
- 计算 n / m n/m n/m 的任意位小数;
- 通过计算的结果构建链表或类似循环链表的结构;
- 根据链表或类似循环链表的结构输出。
计算任意精度的小数
有穷小数
重温除法的竖式计算过程,本质上是整除与取余数的过程,在除法的每一步中商作为结果对应位的数,余数乘以十后作为下一步的被除数。以 2 / 7 2/7 2/7 举例,第一步求取个位的结果,由于 2 ÷ 7 = 0 … 2 2\div7=0\dots2 2÷7=0…2,所以个位数为 0 0 0,余下 2 2 2,添加 0 0 0 成为 20 20 20 作为下一次除法的被除数;第二部求取十分位的结果,由于 20 ÷ 7 = 2 … 6 20\div7=2\dots6 20÷7=2…6,所以十分位的数为 2 2 2,余下的 6 6 6 添 0 0 0 成为 60 60 60 作为下一步运算的被除数,以此类推直到余数为 0 0 0 即除法结束。
可以给出 C++
代码如下
/* 分别是被除数,除数,余数和商 */
int dividend, divisor, remainder, quotient;
/* 读取dividend, divisor的值 */
dividend *= 10; // 直接开始十分位的运算
do {
quotient = dividend / divisor; // 求取商数
remainder = dividend % divisor;// 求取余数
dividend = remainder * 10; // 求下一步的被除数
printf("%d", quotient);
} while (remainder == 0)
显然根据上述代码,如果某个分数可以化为有限长度的小数,无论这个数有多长,都可以得到这个数并输出在屏幕上。
无穷小数
如果这个分数所对应的小数是无限长度的,那么这个循环注定是不能停止的。但是我们应当注意到另一条性质
n / m n/m n/m 如果是无穷长度的小数,那么这个小数必然是循环小数。
只要能够找到这个小数的循环节,那么我们仍然能够精确地表达这个分数所对应的小数。这样关注的重点就变成了如何判断循环节。再次回顾除法的竖式运算过程,可以发现每次计算一位结果的时候都会产生一个余数,同时这个结果的产生完全依赖于上一次运算所产生的余数,那么一次记录产生的余数,并时刻检查余数是否重复,就可以检验是否出现了循环节。
仍然以 2 ÷ 7 2\div7 2÷7为例
小数第 i i i 位 | 除法过程 | 数位结果 | 余数结果 |
---|---|---|---|
个位 | 2 ÷ 7 = 0 … 2 2\div7=0\dots2 2÷7=0…2 | 0 | 2 |
1 | 20 ÷ 7 = 2 … 6 20\div7=2\dots\bf6 20÷7=2…6 | 2 | 6 |
2 | 60 ÷ 7 = 8 … 4 60\div7=8\dots4 60÷7=8…4 | 8 | 4 |
3 | 40 ÷ 7 = 5 … 5 40\div7=5\dots5 40÷7=5…5 | 5 | 5 |
4 | 50 ÷ 7 = 7 … 1 50\div7=7\dots1 50÷7=7…1 | 7 | 1 |
5 | 10 ÷ 7 = 1 … 3 10\div7=1\dots3 10÷7=1…3 | 1 | 3 |
6 | 30 ÷ 6 = 4 … 2 30\div6=4\dots2 30÷6=4…2 | 4 | 2 |
7 | 20 ÷ 7 = 2 … 6 20\div7=2\dots\bf6 20÷7=2…6 | 2 | 6 |
注意列式中表粗体的两个余数 6 6 6,每当遇到余数 6 6 6 时,接下来的六步产生的运算结果必然是 8 , 5 , 7 , 1 , 4 , 2 , 2 8,5,7,1,4,2,2 8,5,7,1,4,2,2,而最后一步产生的余数仍然是 6 6 6,从而造成这个运算结果无限循环下去,由此可见,我们可以通过余数是否重复来判断是否存在循环节,通过第一个重复的余数来确定循环节开始和结束的位置,由此得到 2 ÷ 7 = 0. 2 ˙ 85714 2 ˙ 2\div7=0.\dot285714\dot2 2÷7=0.2˙857142˙。
我们可以将每一步运算的余数和对应的小数位数记录在一个结构体中,将这个结构体放在某个数据集中(可以是线性表,也可以是二叉树等更加高效的储存结构),每次得到一个余数就查询这个数据集中是否存在余数相同的结构体,如果不存在则将这个余数和其对应的小数位数组成的结构体加入到数据集中,否则直接读取数据集中具有相同余数的结构体所记录的小数位数,从这个小数位数一直到当前计算的位数的所有小数构成循环节。
构建链表或循环链表
这里通过链表的方式来输出小数。
前置代码为
/* NODE结构体,用来储存小数的每一位 */
typedef struct NODE {
int quotient;
struct NODE *next;
} NODE;
/* RNODE结构体,用来储存出现过的余数 */
typedef struct RNODE {
int remainder;
/* node属性指向对应的NODE节点 */
struct RNODE *node;
} RNODE;
/* 返回一个NODE指针,指针指向的对象的quotient属性为value,next属性为NULL */
NODE *makeNode(int value) {
NODE *tmp = (NODE*)malloc(sizeof(NODE));
tmp->next = NULL;
tmp->quotient = value;
return tmp;
}
ChainTable resultChain; // 用于储存结果的链表
DataSet remainderSet; // 用于储存余数的数据集
有穷小数
有穷小数的链表储存非常容易,直接然最后一个链表元素指向 NULL
即可。
/* 分别是被除数,除数,余数和商 */
int dividend, divisor, remainder, quotient;
/* 读取dividend, divisor的值 */
dividend *= 10; // 直接开始十分位的运算
do {
quotient = dividend / divisor; // 求取商数
remainder = dividend % divisor;// 求取余数
dividend = remainder * 10; // 求下一步的被除数
resultChain.push(makeNode(quotient));// 链表元素插入,表示计算出的当前小数位进入到链表中
} while (remainder == 0)
无穷小数
在这种情况下,每次计算完之后都需要进行一次检查。
/* 分别是被除数,除数,余数和商 */
int dividend, divisor, remainder, quotient;
/* 读取dividend, divisor的值 */
dividend *= 10; // 直接开始十分位的运算
do {
quotient = dividend / divisor; // 求取商数
remainder = dividend % divisor;// 求取余数
dividend = remainder * 10; // 求下一步的被除数
// 寻找当前余数是否出现过,如果出现返回指向该元素的指针,否为返回NULL
RNODE *rtmp = remainderSet.retrieve(remainder);
NODE *ntmp = makeNode(quotient);
resultChain.push(ntmp);
if (rtmp != NULL) {/* 当前余数出现 */
ntmp->next = rtmp->node->next;
break;
}
else /* 当前余数未出现 */
remainderSet.push({remainder, ntmp});
} while (remainder == 0)
容易验证,适用无穷小数的代码也适用于有穷小数。
输出
沿着链表输出到结尾或者输出到指定位数,即可。可以看看题目的预置代码中的 output
函数。
/* PRESET CODE BEGIN - NEVER TOUCH CODE BELOW */
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int data;
struct node *next;
} NODE;
void output(NODE *, int);
void change(int, int, NODE *);
void output(NODE *head, int kk)
{
int k = 0;
printf("0.");
while (head->next != NULL && k < kk)
{
printf("%d", head->next->data);
head = head->next;
k++;
}
printf("\n");
}
int main()
{
int n, m, k;
NODE *head;
scanf("%d%d%d", &n, &m, &k);
head = (NODE *)malloc(sizeof(NODE));
head->next = NULL;
head->data = -1;
change(n, m, head);
output(head, k);
return 0;
}
/* PRESET CODE END - NEVER TOUCH CODE ABOVE */
没了。