有理数到浮点数

Posted

技术标签:

【中文标题】有理数到浮点数【英文标题】:Rational to floating point 【发布时间】:2015-10-05 18:26:12 【问题描述】:

考虑一个由以下结构表示的有理数。

struct rational 
    uint64_t n;
    uint64_t d;
    unsigned char sign : 1;
;

假设double 的IEEE-754 binary64 表示,如何通过正确的舍入将结构转换为最接近的double?将nd 转换为double 并清楚地划分它们的简单方法会导致舍入误差。

【问题讨论】:

C 还是 C++?选择一个... 没关系。如果它困扰您,我会将 Java 添加到列表中。 @LightnessRacesinOrbit:据我所知,C 和 C++ 具有相同或足够相似的算术规则(我不了解 Java)。另一方面,我同意指定实际使用的语言是一件好事。另一方面,如果这只是一道数学题,为什么会出现在编程网站上? 虽然我担心 OP 可能无意中引起了三个优秀的大型 SO 子社区的愤怒,但这些问题非常有效、有趣,而且实际上大部分与语言无关。但这并不意味着它与编程无关,因为 IEEE 754 标准非常关注编程语言的可用性,或多或少地附加了包袱。 @KeithThompson:显然,“java”标签是由于幼稚的恶意而出现的。 【参考方案1】:

实现所需结果的一种方法是在整数空间中执行除法。由于标准 C/C++ 不提供 128 位整数类型(而某些工具链可能会提供此作为扩展),这不是很有效,但会产生正确的结果。

下面的代码生成 54 个商位和一个余数,一次一位。最高有效的 53 个商位表示 double 结果的尾数部分,而最低有效商位和余数是根据 IEEE-754 舍入到“最接近或偶数”所需要的。

下面的代码可以编译为 C 或 C++ 程序(至少在我的工具链中是这样)。它已经过轻微测试。由于按位处理,这不是很快,并且可以进行各种优化,尤其是在使用特定于机器的数据类型和内在函数的情况下。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

struct rational 
    uint64_t n;
    uint64_t d;
    unsigned char sign : 1;
;

double uint64_as_double (uint64_t a)

    double res;
#if defined (__cplusplus)
    memcpy (&res, &a, sizeof (res));
#else /* __cplusplus */
    volatile union 
        double f;
        uint64_t i;
     cvt;
    cvt.i = a;
    res = cvt.f;
#endif /* __cplusplus */
    return res;


#define ADDcc(a,b,cy,t0,t1) (t0=(b), t1=(a), t0=t0+t1, cy=t0<t1, t0=t0)
#define ADDC(a,b,cy,t0,t1) (t0=(b)+cy, t1=(a), t0+t1)
#define SUBcc(a,b,cy,t0,t1) (t0=(b), t1=(a), cy=t1<t0, t1-t0)

double rational2double (struct rational a)

    uint64_t dividend, divisor, quot, rem, t0, t1, cy, res, expo;
    int sticky, round, odd, sign, i;

    dividend = a.n;
    divisor = a.d;
    sign = a.sign;

    /* handle special cases */
    if ((dividend == 0) && (divisor == 0)) 
        res = 0xFFF8000000000000ULL; /* NaN INDEFINITE */
     else if (dividend == 0)             
        res = (uint64_t)sign << 63; /* zero */
     else if (divisor == 0) 
        res = ((uint64_t)sign << 63) | 0x7ff0000000000000ULL; /* Inf */
     
    /* handle normal cases */
    else 
        quot = dividend;
        rem = 0;
        expo = 0;
        /* normalize operands using 128-bit shifts */
        while (rem < divisor) 
            quot = ADDcc (quot, quot, cy, t0, t1);
            rem = ADDC (rem, rem, cy, t0, t1);
            expo--;
        
        /* integer bit of quotient is known to be 1 */
        rem = rem - divisor;
        quot = quot + 1;
        /* generate 53 more quotient bits */
        for (i = 0; i < 53; i++) 
            quot = ADDcc (quot, quot, cy, t0, t1);
            rem = ADDC (rem, rem, cy, t0, t1);
            rem = SUBcc (rem, divisor, cy, t0, t1);
            if (cy) 
                rem = rem + divisor;
             else 
                quot = quot + 1;
            
        
        /* round to nearest or even */
        sticky = rem != 0;
        round = quot & 1;
        quot = quot >> 1;
        odd = quot & 1;
        if (round && (sticky || odd)) 
            quot++;
        
        /* compose normalized IEEE-754 double-precision number */
        res = ((uint64_t)sign << 63) + ((expo + 64 + 1023 - 1) << 52) + quot;
    
    return uint64_as_double (res);

【讨论】:

谢谢。当我意识到长除法产生了精确的数字时,我就知道解决方案应该是这样的。干杯!

以上是关于有理数到浮点数的主要内容,如果未能解决你的问题,请参考以下文章

将逗号添加到浮点数或字符串?

MODBUS RTU协议中浮点数是如何存储,读到浮点数寄存器的数值如何转换成所需的浮点数

在 Python 中将 numberDecimal 值投影到浮点数中

从字符串到浮点数与python中的argv

将值列表映射到浮点数时出现mypy错误

支持小数点和小数点逗号的字符串到浮点数转换