将十进制数转换为合理的等效数时的精度问题

时间:2018-02-11 11:08:25

标签: c++ c double rational-number

我有将双重(比如 N )转换为p / q形式(理性形式)的问题,为此我有以下策略:

  1. 将N倍乘以大数字表示$ k = 10 ^ {10} $
  2. 然后p = y*kq = k
  3. 选择gcd(p,q)并找到p = p/gcd(p,q)q = p/gcd(p,q)
  4. N = 8.2时,如果我们使用笔和纸解决问题,答案是正确的,但8.28.19999999(双倍)中表示为N,则会导致问题它的理性形式转换。

    我尝试了其他方式:(我使用了大号10 ^ k而不是100)

    if(abs(y*100 - round(y*100)) < 0.000001) y = round(y*100)/100

    但是这种方法也没有给出正确的表示。

    有什么方法可以执行从double到p / q的等效转换?

2 个答案:

答案 0 :(得分:1)

浮点运算非常困难。正如评论中提到的,部分困难在于你需要用二进制表示你的数字。

例如,数字0.125可以用二进制表示:

0.125 = 2^-3 = 0b0.001

但数字0.12不能。

至11位重要人物:

0.12 = 0b0.00011110101

如果将其转换回小数,则错误变得明显:

0b0.00011110101 = 0.11962890625

所以如果你写:

double a = 0.2;

机器实际上做的是找到最接近的二进制表示,它可以在双数据类型中保存。这是一个近似值,因为如上所述,0.2不能用二进制精确表示。

一种可能的方法是定义一个“ε”。它确定您的数字与最近的可表示二进制浮点的接近程度。

这是一篇关于浮点的好文章:

https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

答案 1 :(得分:1)

  

有将双倍(例如 N )转换为p / q形式的问题    ......当N = 8.2

典型的double无法正确编码8.2 。相反,最接近的可表示double约为

8.19999999999999928945726423989981412887573...
8.20000000000000106581410364015027880668640...  // next closest

当代码

double N = 8.2;

8.19999999999999928945726423989981412887573...转换为合理形式。

double转换为p / q格式:

  

将N倍乘以大数字表示$ k = 10 ^ {10} $

这可能会溢出double。第一步应该是确定double是否很大,它是一个整数。

double当然使用二进制编码时,不要乘以10的幂。乘以10,100等可能会引入舍入误差。

double的C实现压倒性地使用二进制编码,因此FLT_RADIX == 2

然后每个有限double x都有一个有效数,它是某个整数超过2的幂的一部分:DBL_MANT_DIG数字@Richard Critten的二进制分数。这通常是53位二进制数字。

确定double的指数。如果足够大或x == 0.0,则double是一个整数。

否则,按DBL_MANT_DIG缩放分子和分母。虽然分子是偶数,但分子和分母都减半。由于denominator是2的幂,因此简化考虑不需要其他素数值。

#include <float.h>
#include <math.h>
#include <stdio.h>

void form_ratio(double x) {
  double numerator = x;
  double denominator = 1.0;
  if (isfinite(numerator) && x != 0.0) {
    int expo;
    frexp(numerator, &expo);
    if (expo < DBL_MANT_DIG) {
      expo = DBL_MANT_DIG - expo;
      numerator = ldexp(numerator, expo);
      denominator = ldexp(1.0, expo);
      while (fmod(numerator, 2.0) == 0.0 && denominator > 1.0) {
        numerator /= 2.0;
        denominator /= 2.0;
      }
    }
  }
  int pre = DBL_DECIMAL_DIG;
  printf("%.*g --> %.*g/%.*g\n", pre, x, pre, numerator, pre, denominator);
}

int main(void) {
  form_ratio(123456789012.0);
  form_ratio(42.0);
  form_ratio(1.0 / 7);
  form_ratio(867.5309);
}

输出

123456789012 --> 123456789012/1
42 --> 42/1
0.14285714285714285 --> 2573485501354569/18014398509481984
867.53089999999997 --> 3815441248019913/4398046511104