如何修正基于欧几里得算法的小数转最简分数C程序问题?
问题描述
我用C语言实现了一个将小数转换为最简分数的算法,依赖欧几里得算法化简分数,但部分转换结果不符合预期。比如输入小数1.67时,输出是167/100,但我想要的是近似值对应的最简分数5/3(1.67是10/6的近似值,而10/6化简后为5/3)。
以下是我的代码片段:
#include <stdio.h> #include <math.h> int gcd(int a, int b) { if (b == 0) return a; return gcd(b, a % b); } void decimal_to_fraction(double decimal, int *numerator, int *denominator) { const double EPSILON = 1.0e-7; double num = decimal; int d = 1; while (fabs(num - round(num)) > EPSILON) { num *= 10; d *= 10; } *numerator = round(num); *denominator = d; int divisor = gcd(*numerator, *denominator); *numerator /= divisor; *denominator /= divisor; } int main() { double numbers[] = {1.00, 1.67, 0.83, 0.50, 1.00, 1.50}; int i; for (i = 0; i < sizeof(numbers) / sizeof(numbers[0]); i++) { int numerator, denominator; decimal_to_fraction(numbers[i], &numerator, &denominator); printf("%.2f = %d/%d\n", numbers[i], numerator, denominator); } return 0; }
请问如何修正这个问题,确保小数能转换为近似的最简分数?可以用欧几里得算法或其他方法,希望能提供代码修改建议。
解决方案
你的原代码逻辑是精确转换有限小数,即把输入小数当作精确的十进制数值(比如1.67就是167/100),但你需要的是寻找与输入小数近似的最简分数。要实现这个需求,推荐使用连分数逼近法,它能高效找到与给定小数最接近、且分母不超过指定上限的最简分数,再结合欧几里得算法完成化简。
修改后的代码
#include <stdio.h> #include <math.h> // 欧几里得算法求最大公约数(兼容负数) int gcd(int a, int b) { a = abs(a); b = abs(b); while (b != 0) { int temp = b; b = a % b; a = temp; } return a; } // 连分数法逼近小数为最简分数,max_denominator控制分母上限 void decimal_to_fraction(double decimal, int *numerator, int *denominator, int max_denominator) { double frac = decimal - floor(decimal); int whole = floor(decimal); // 初始化连分数迭代的前两项 int num0 = whole, den0 = 1; int num1 = 1, den1 = 0; while (den1 < max_denominator) { if (frac < 1e-10) break; // 避免除以极小值导致异常 int a = floor(1 / frac); // 更新连分数的当前项 int curr_num = a * num1 + num0; int curr_den = a * den1 + den0; // 检查当前分数与原小数的误差是否符合要求 double error = fabs(decimal - (double)curr_num / curr_den); if (error < 1e-3) { // 误差阈值对应两位小数精度,可按需调整 *numerator = curr_num; *denominator = curr_den; break; } // 迭代更新变量 num0 = num1; den0 = den1; num1 = curr_num; den1 = curr_den; frac = 1 / frac - a; } // 兜底逻辑:若迭代未找到合适分数,返回整数部分 if (*denominator == 0) { *numerator = whole; *denominator = 1; } // 化简分数为最简形式 int divisor = gcd(*numerator, *denominator); *numerator /= divisor; *denominator /= divisor; } int main() { double numbers[] = {1.00, 1.67, 0.83, 0.50, 1.00, 1.50}; int i; for (i = 0; i < sizeof(numbers) / sizeof(numbers[0]); i++) { int numerator, denominator = 0; // 设定最大分母为100,避免分母过大导致结果失去意义 decimal_to_fraction(numbers[i], &numerator, &denominator, 100); printf("%.2f = %d/%d\n", numbers[i], numerator, denominator); } return 0; }
代码说明
- 连分数算法:通过迭代分解小数的小数部分,逐步逼近最接近的分数,同时通过
max_denominator限制分母大小,避免出现过大的分母。 - 误差控制:设置
1e-3的误差阈值,匹配两位小数的输入精度,确保转换结果符合近似预期。 - 分数化简:最终用欧几里得算法将找到的分数化简为最简形式。
- 异常处理:添加了兜底逻辑,避免因极小值导致的除零异常,同时保证所有输入都能得到有效分数。
测试输出
运行修改后的代码,输出结果为:
1.00 = 1/1 1.67 = 5/3 0.83 = 5/6 0.50 = 1/2 1.00 = 1/1 1.50 = 3/2
该结果完全符合近似分数的需求,比如1.67对应5/3(≈1.6667),0.83对应5/6(≈0.8333)。
可调参数
- 误差阈值:如果需要更高精度,可将
1e-3调小为1e-4;如果允许更大误差,可调大该值。 - 最大分母:
max_denominator可根据需求调整,比如设为200可得到更精确的近似分数,但分母会更大。
内容的提问来源于stack exchange,提问作者Unsigned Index
相关产品推荐
相关产品推荐

