C语言 将十进制数转换为分数

z9zf31ra  于 2023-11-16  发布在  其他
关注(0)|答案(7)|浏览(157)

我正在尝试将十进制数转换为小数。十进制数的小数位后最多有4位。例如:- 12.34 = 1234/100 12.3456 = 123456/10000
密码:

#include <stdio.h>
int main(void) {
  double a=12.34;
  int c=10000;
  double b=(a-floor(a))*c;
  int d=(int)floor(a)*c+(int)b; 
  while(1) {
     if(d%10==0) {
    d=d/10;
    c=c/10;
 }
 else break;
  }
  printf("%d/%d",d,c);
 return 0;
}

字符串
但我没有得到正确的输出,十进制数将只有双精度。请指导我应该怎么做。

qltillow

qltillow1#

如果你的浮点数是x,那么超过10000的分数的分子将是(x + 0.00005) * 10000的整数部分。这取决于你是否要将分数简化为最简单的项(即除以分子和分母的gcd)。

dsekswqp

dsekswqp2#

#include <stdio.h>

int main(void) {
    double a = 12.34;
    int c = 10000;
    double b = (a - floor(a)) * c;
    int d = (int)floor(a) * c + (int)(b + .5f); 
    printf("%f %d\n", b, d);

    while(1) {
       if(d % 10 == 0) {
           d = d / 10;
           c = c / 10;
       }
       else break;
    }
    printf("%d/%d\n", d, c);
    return 0;
}

字符串
问题是b得到的是3400.00,但是当你执行(int) b时,你得到的是3399,所以你需要加上0.5,这样数字就可以截断为3400。
得到3400.00与得到3400不同,3400.00意味着数字被舍入到3400,这就是为什么当你做(int)3400.00时,它假设最接近的整数(小于你正在转换的数字)是3399,但是,当你向那个数字添加0.5时,最后一个最接近的整数现在是3400。
如果你想更深入地了解浮点运算,请阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic

eiee3dmh

eiee3dmh3#

我的解决方案很简单,“懒惰”,通过迭代运行,没有什么花哨的。
在大多数有一个不错的数学库的语言中,你只需要算法本身。
但是在bc中,您需要实现一些简单的函数,例如

int() to return integer part of a number ,
abs() to return absolute value ,
float() to return floating part of a number ,
round() to round to nearest integer.

字符串
如果在(1/eps)次迭代后没有找到任何东西,则循环将以最后一个结果中断。

eps=10^-4 /*Tweak for more or less accuracy */

define int(x) {
  auto s ;
  s = scale ;
  scale = 0 ;
  x /= 1 ;
  scale = s ;
  return x ;
}
define round(x) { return int(x+.5-(x<0)) ; }
define abs(x) { if ( x < 0 ) x=-x ; return x ; }
define float(x) { return abs(x-int(x)) ; }

define void frac(x) {
    auto f, j, n, z ;
    f = float(x) ;    
    j = 1 / eps ;
    z = .5 ;
    if ( f != 0 ) {
        while ( ( n++ < j ) && ( abs( z - round(z) ) > eps ) ) z = n / f ;
        n -= 1 ;
        if ( x < 0 ) n = -n ;
        x = int(x)
        z = round(z) ;
        print n + x*z , "/" , z , " = "
        if ( x != 0 )  print x , " + " , n , "/" , z , " = "
    }
    print x+n/z , "\n" ;
}


在标准精度(eps=.0001)下,可以得到:

frac(-.714285)
-5/7 = -.71428571428571428571

sqrt(2)
1.414213562373
frac(sqrt(2))
19601/13860 = 1 + 5741/13860 = 1.414213564213

6-7/pi
3.77183080
eps=.000001 ; frac(6-7/pi)
1314434/348487 = 3 + 268973/348487 = 3.77183080

n53p2ov0

n53p2ov04#

下面是我使用的算法。它是一个迭代过程,工作原理如下:
1.分子的初始近似值为1,分母为1除以浮点值的小数部分。例如,当将0.06转换为小数时,分母= 1/0.06 = 16.6666667(四舍五入为17),因此初始近似值为1/17。
1.计算浮点值和当前近似值之间的差值。例如,差值为1/17 - 0.06 = 0.058824 - 0.06 =-0.001176。
1.如果差值的绝对值小于定义的公差(即0.000005),则迭代终止。
1.使用步骤2中计算的差值来改进分数的近似。这是通过将差值转换为分数并添加(或减去)到当前近似值。在示例中,负差值表示低近似值--因此需要将差值添加到当前近似值中。差值分数为分子= 1,分母= 1/0.001176 = 850 --分数差为1/850。新的近似值为(1/17)+(1/850)=(8501 + 171)/(850*17)= 867/14450。
1.重复步骤2至4,直到找到解决方案。
1.在找到解之后,分数可以被减小。例如,867/14450正好是0.06,并且迭代过程终止。867/14450可以被减小到3/50。
这种方法的一些特点是:

  • 如果得到的分数是1/任何值,则第一近似值将是精确的。例如,将0.25转换为分数,则第一近似值将是1/4。因此不需要进一步的迭代。
  • 在1,000,000个测试用例中的大多数(> 80%),收敛发生在2次迭代或更少。
  • 对于所有测试用例,最大迭代次数为3。

我在github上发布了这个算法的代码--https://github.com/tnbezue/fraction

wz1wpwve

wz1wpwve5#

这是一个有趣的问题。我想你可能最好从阅读关于计算“最大公约数”的倍数方法开始(http://en.wikipedia.org/wiki/Greatest_common_divisor是一个很好的来源)。
实现一个快速和肮脏的算法,使这些计算就像你用笔和纸做的那样,然后研究如何表示双精度(符号,指数,尾数),并改进你的算法以利用这种表示。
不幸的是,如果不写你的代码,我也做不了什么。

ulmd4ohb

ulmd4ohb6#

一个用c++创建的算法,可以从小数到分数。

#include <iostream>
using namespace std;

// converts the string half of the inputed decimal number into numerical values
void converting (string decimalNumber, float& numerator, float& denominator )

{
float number;
string valueAfterPoint = decimalNumber.substr(decimalNumber.find(".") + 1,((decimalNumber.length() -1) )); // store the value after the decimal into a valueAfterPoint
cout << valueAfterPoint<< " "<< endl;
int length = valueAfterPoint.length(); //stores the length of the value after the decimal point into length

 numerator = atof(valueAfterPoint.c_str()); // converts the string type decimal number into a float value and stores it into the numerator

// loop increases the decimal value of the numerator and the value of denominator by multiples of ten as long as the length is above zero of the decimal

cout << length<< endl;
for (; length > 0; length--)
{
    numerator *= 10;

}
do
    denominator *=10;
    while  (denominator < numerator);

}

// simplifies the the converted values of the numerator and denominator into simpler values for          an easier to read output
  void simplifying (float& numerator, float& denominator)
{
int maximumNumber = 9; //Numbers in the tenths place can only range from zero to nine so the maximum number for a position in a poisitino for the decimal number will be nine

bool isDivisble; // is used as a checker to verify whether the value of the numerator has the       found the dividing number that will a value of zero

// Will check to see if the numerator divided denominator is will equal to zero


if(int(numerator) % int(denominator) == 0)
{
    numerator /= denominator;
    denominator = 1;
    return;
}

//check to see if the maximum number is greater than the denominator to simplify to lowest form
while (maximumNumber < denominator)
{
    maximumNumber *=10;
 }

// the maximum number loops from nine to zero. This conditions stops if the function isDivisible is true
for(; maximumNumber > 0; maximumNumber --)
{

    isDivisble = ((int(numerator) % maximumNumber == 0) && int(denominator)% maximumNumber == 0);
    cout << numerator << denominator <<" " <<endl;
    if(isDivisble)
    {
        numerator /= maximumNumber;  // when is divisible true numerator be devided by the max    number value for example 25/5 = numerator = 5

        denominator /= maximumNumber; //// when is divisible true denominator be devided by the max number value for example 100/5 = denominator = 20

    }
    // stop value if numerator and denominator is lower than 17 than it is at the lowest value
    int stop = numerator + denominator;

    if (stop < 17)
    {
        return;
    }
}
}
  int main()
{
string decimalNumber;
float numerator = 0;
float denominator = 1;

cout << "Enter the decimal number";
cin >> decimalNumber;

//convert function
converting(decimalNumber, numerator, denominator);

//call simplyfication funcition
simplifying(numerator, denominator);

cout<< "Fraction: "<< numerator << "/" << denominator<< endl;
 return 0; 

}

字符串

6ojccjat

6ojccjat7#

正确的解决方案是使用continued fraction。用与问题相关的可理解的术语总结维基百科文章:每个十进制数都可以用一个连续的分数表示,达到所需的准确度,然后计算为:一个整数,一个分子和一个分母(即三个独立的整数值)。连续的分数也恰好产生完全约化的分数。
第一步是从浮点数生成连分数项。下面是广义伪代码,其中假设输入数为正数(支持负数作为练习,但我们实际上只关心小数部分,例如0.34):

val = 12.34;

    val2 = floor(val);

    result = new vector();
    result.append(val2);

    val -= val2;
    mval = 1;

    // Adjust these estimated levels for your desired precision.
    // mval imperfectly estimates the maximal number of digits of the denominator.
    // Both thresholds are necessary to avoid an infinite loop.
    while (val > 1.0e-4 && mval < 1.0e+4)
    {
        val = 1 / val;
        mval = mval * val + 1;

        val2 = floor(val);
        result.append(val2);
        val -= val2;
    }

字符串
下面是结果向量,其中包含12.34至约4位小数精度的连分数项:

[12, 2, 1, 16]


下一步是通过向后遍历生成的向量/数组并建立分子和分母来执行连分数计算本身。下面是使用上面的术语填写的连分数公式:

12+(1 /(2+(1 /(1+(1 /16))

简化后的计算如下:

12 + (1 / (2 + (1 / (1 + (1 / 16)))))
12 + (1 / (2 + (1 / ((16 + 1) / 16)))))
12 + (1 / (2 + (1 / (17 / 16))))
12 + (1 / (2 + (16 / 17)))
12 + (1 / ((34 + 16) / 17))
12 + (1 / (50 / 17))
12 + 17 / 50


17/50也恰好是不可约的,因为17是一个素数,这有助于验证前面的陈述,即连分数是完全约化的。
上面的数学 * 看起来 * 很复杂,但上面计算的伪代码非常简单:

num = 1;
    den = result[result.length - 1];

    for (x = result.length - 1; x > 1; x--)
    {
        num2 = result[x - 1] * den + num;

        num = den;
        den = num2;
    }

    // If you want just a fraction (i.e. no whole number):
    // num += result[0] * den;


先前向量的示例值:

result[0] = 12, num = 17, den = 50.


正如在其他地方所指出的,浮点数是不精确的。这可能导致在连分数result向量中出现非常大的数字,即使上面的阈值取决于输入的值。您必须决定什么精度阈值对您的应用程序有意义。
您始终可以过度生成result矢量精度(例如1.0e-15)和估计的最大值(例如1.0e15),然后基于硬分母限制多次执行连续分数计算步骤,直到不再超过您设置的阈值。
作为应用上述过度生成技术的一个示例,假设您将分母的硬限制设置为49。测试上面的完整向量的结果为17/50,正如我们之前看到的那样。由于50大于49,因此我们可以从向量中删除最后一项并再次尝试连分数计算:

12+(1 /(2+(1 /1

12 + (1 / (2 + (1 / 1)))
12 + (1 / (2 + 1))
12 + 1 / 3


12.333333是12.34的一个相当好的估计(在一定的误差范围内),分母3现在福尔斯在所需的最大分母阈值49内。作为旁注,1/3也恰好是不可约的。如果分母仍然不够小,只要不断地删除末尾的项并重新计算,直到它足够小。删除项会增加误差量,但也会减少分母的值。
各种图形计算器,甚至Microsoft Excel都以这种方式使用连分数来显示浮点数作为分数。我也见过一些在线连分数计算器。因此,您可以利用这些工具来确认您自己的实现是否正常运行。

相关问题