如何将ieee754双倍转换为两个整数的分数?

时间:2013-04-30 10:20:26

标签: c floating-point

如何从long long(64位)值a获取b(64位)值doubled,以便{ {1}}或多或少等于(double)a / b?这是否可能(不会损失精确度)?

我已经尝试过这个但是它没有得到任何地方,所以我想也许我的想法是错误的:

d

4 个答案:

答案 0 :(得分:5)

除了无穷大和NaN之外,每个浮点数都可以精确地表示为两个整数的比率。一些双精度浮点数确实需要大于64位的整数 - 例如,1e-300将转换为6032057205060441 / (2 ** 1049)。但是,大约范围(2**-40, 2**63)内的浮点数可以无损地转换为两个64位整数的一小部分。

这种转换函数的一个例子是Python的as_integer_ratio()方法,它的浮点对象。从Python / C-ese翻译,代码如下所示:

#include <math.h>
#include <stdlib.h>

void double_as_ratio(double flt, long long *numerator, long long *denominator)
{
    double float_part;
    int exponent;
    long long long_exponent;
    int i;

    float_part = frexp(flt, &exponent);  /* flt == float_part * 2**exponent exactly */
    for (i=0; i<300 && float_part != floor(float_part) ; i++) {
        float_part *= 2.0;
        exponent--;
    }
    /* flt == float_part * 2**exponent exactly and float_part is integral. */

    *numerator = (long long) float_part;           /* can overflow */
    long_exponent = 1LL << labs((long) exponent);  /* can overflow */
    if (exponent > 0) {
        *numerator *= long_exponent;
        *denominator = 1;
    }
    else
        *denominator = long_exponent;
}

此代码不依赖于位的确切布局,仅取决于C89所需的frexpfloor函数。应用于浮点值0.1,它会生成360287970189639736028797018963968的正确值。

答案 1 :(得分:2)

你到底想要做什么?如果您要将double转换为理性,那么您几乎肯定需要一个近似的答案。

您希望它有多准确?如果答案恰好是244653797/159601597,您希望这是答案吗?我非常怀疑。你想要小数部分吗?还是具有小数分母的分数?或者是什么?

0.4286应该是4286/10000 = 2143/500还是1/7?

0.428应该是107/250还是1/7?

在不知道您实际想要解决的问题的情况下,很难解决它。

答案 2 :(得分:1)

丹·斯蒂芬写道code for rational reconstruction这是非常简单和有益的阅读。如果您希望ab以便a/b轮到正确的doubleb相当小,请在调用时设置bits参数reconstruct_bits到53。

我相信代码通过连续的分数近似来工作。值得注意的是,对于给定的分母界限或相对误差界限,这并不一定产生 最佳有理逼近。它产生了所有合理的重建,最大限度地减少了所有可能的分母边界的相关数量(目前无法实现)。

答案 3 :(得分:0)

基于评论,其他一些答案以及纯粹的追踪和错误,我想出了这个(似乎或多或少的工作):

union ieee754_double u;
u.d = d;

long long a = (long long)(!u.ieee.exponent && u.ieee.exponent != 0x7ff) << 52 |
        (long long)u.ieee.mantissa0 << 32 | u.ieee.mantissa1;
int exp = IEEE754_DOUBLE_BIAS - u.ieee.exponent + 52;

long long b;
if (u.ieee.exponent != 0x7ff) {
    if (exp > 62) {
        a >>= exp - 62;
        exp = 62;
    }
    if (exp < 0) {
        a <<= 0 - exp;
        exp = 0;
    }
    b = 1LL << exp;
} else {
    b = 0;
}

a = u.ieee.negative ? -a : a;

我仍在试图找出边缘的位置,但由于我正在移出位,我认为它无损,直至+/-2**62并降至{{1} }

使用2**-10 - &gt;进行一些往返测试atof(argv[1])),我似乎能够将值精确地重现到printf()+/-2**53(只要我是圆的)。我猜测2**-182**53的限制。