计算M÷N精确值

本文介绍了一种通过编程计算分数n/m(n,m为正整数)的精确值的方法,包括整除情况和无限循环小数情况。针对无限循环小数,详细解释了如何确定循环节的起始位置及长度,并提供了具体实现代码。

   本人是个编程爱好者,同时又是一个数学爱好者,很希望能够用程序去解决一些数学问题。比如我想计算n/m的值(n,m都是正整数),虽然我们可以在float范围内进行除法计算,但所得的值都不够精确。怎么样才能够算出n/m的精确值呢?首先要明白n/m的值有两类,一类是可以整除的,另一类不能整除,但结果是无限循环小数。因此我要的结果是,如果可以整除,输出完整结果;不能整除,输出一个循环节。现在比较难的问题是不能整除的情况,我们怎么知道n/m从第几位开始循环,并且循环节的长度是多少呢?要解决这两个问题需要一些相关的数学知识。

 

   关于循环节长度,看一个例子(本人从网上找来的):

求1/2008的小数形式的循环节长度。

解答:

2008=2^3×251
φ(251)=250
250的正因数有1、2,5、10、25、50、125、250,x取上述正因数并且满足10^x≡1 (mod 251)的最小的x是50,所以1/2008的循环节长度是50。

分析:解答似乎"不知所云",因为这涉及到数论知识。首先,10^φ(m)≡1(mod m),是欧拉函数的性质,在此略去证明。对正整数n,欧拉函数是不大于n的数中与n互质的数的数目。而1/2008的循环节长度等于1/251的循环节长度,因为循环小数乘除2或5的任意幂次,循环节长度不变,乘除的2,5在mod 10时不会体现。10^t≡1(mod n)与循环小数的关系,就是10^t被n除余数为1,这样在除法式中,除到第t位时余数与开始时相同,这意味着新的循环开始了,即t是循环节长度。而251是质数,其欧拉函数为251-1=250。由于250必为循环节长度的整数倍,故一个循环节的长度为250的约数,对约数从小到大逐一试验即可。

 

我们要获得循环节的长度,就要拣出m的所有约数x,并进行验证10^x≡1,则x为循环节长度。

 

   至于从第几位开始循环:分母中含有2的因子个数加上5的因子个数再加上1,即若n=2^i*5^j*p,则从第i+j+1位开始循环。

 

根据以上知识,进行编程,代码如下:

 

#include<stdio.h>

 

#define MAX     1000

 

//求n与m的最大公约数
int gcd(int n, int m)
{
    int temp;
    int r;

    if(n < m)
    {
        temp = n;
        n = m;
        m = temp;
    }

    while(m != 0)
    {
        r = n % m;
        n = m;
        m = r;
    }

    return n;
}


//判断小数从第几位开始循环
//返回0说明是有限小数
int     cirNode(int     n, int      m)
{
    int     i = 1;
    int     g;

    //把n/m化成最简形式
    g = gcd(n, m);
    m = m/g;

    while(m%2 == 0)
    {
        i++;
        m = m/2;
    }
    if(m == 1)
        return 0;

    while(m%5 == 0)
    {
        i++;
        m = m/5;
    }
    if(m == 1)
        return 0;

    return i;

}

 

//欧拉函数:小于n并且与n互素的个数
int     Eulur(int   n)
{
    int     i;
    int     m;

    m = 0;
    for(i=1; i<n; i++)
        if(gcd(n, i) == 1)
            m++;

    return m;
}

 

//计算n/m循环节的长度
int     cirLength(int   n, int  m)
{
    int     l;  //循环节的长度
    int     i, j, k, e, g, t;
    int     a[MAX];

    l = i = j = t =0;

    //把n/m化成最简形式
    g = gcd(n, m);
    m = m/g;

    //去掉因素2和5
    while(m%2 == 0)
        m = m/2;
    while(m%5 == 0)
        m = m/5;

    t = Eulur(m);

    for(i=1; i<=t; i++) //求t的所有约数
        if(t%i == 0)
            a[j++] = i;

    for(i=0; i<j; i++)
    {
        e = 1;
        for(k=1; k<=a[i]; k++)
        {
            e = (10*e)%m;   // (10^a[i] mod m) == 1
        }

        if(e == 1)
        {
            l = a[i];
            return l;
        }
    }

}

 

int     szDiv[MAX]; //保存计算结果,szDiv[0]为整数部分,其余为小数部分
int     num = 0; //szDiv数组的有效位

void    divide(int  n, int  m)
{
    int     i, j, k, len;

    szDiv[0] = n/m;
    n = n%m;
    if(n == 0)  // n/m刚好整除,没有小数
    {
        return;
    }
    else if(cirLength(n, m) == 0) //有限小数
    {
        i = 1;
        do{
            n = n*10;
            szDiv[i++] = n/m;
            num++;
            n = n%m;
        }while(n != 0);
    }
    else    //无限小数,包括纯循环与混循环小数
    {
        j = cirNode(n, m);
        k = cirLength(n, m);
        len = j+k-1;
        for(i = 1; i<=len; i++)
        {
            szDiv[i] = n*10/m;
            num++;
            n = (n*10)%m;
        }
    }

}


int     main()
{
    int     m, n, i, p;

    printf("n/m:");
    scanf("%d/%d", &n, &m);
    printf("%d/%d=", n, m);

    divide(n, m);

    if(num == 0)
        printf("%d/n", szDiv[0]);
    else
    {
        printf("%d.", szDiv[0]);
        for(i=1; i<=num; i++)
        {
            printf("%d", szDiv[i]);
            if(i%50 == 0)  //每50个数一行
                printf("///n");
        }
        printf("/n");

        p = cirNode(n, m);
        if(p != 0)
        {
            printf("Circle Node is:");  //打印循环节
            for(i=p; i<=num; i++)
            {
                printf("%d", szDiv[i]);
                if((i-p+1)%50 == 0)  //每50个数一行
                    printf("///n");
            }
            printf("/n");
        }

    }

    return 0;
}

 

 

运行结果:

n/m:1/2008
1/2008=0.00049800796812749003984063745019920318725099601593/
625
Circle Node is:49800796812749003984063745019920318725099601593625/

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值