如何在c ++中编写好的round_double函数?

时间:2011-04-14 06:58:15

标签: c++ c double rounding

我正在尝试编写好的round_double函数,它将以指定的精度舍入double:

1

double round_double(double num, int prec)
{
    for (int i = 0; i < abs(prec); ++i)
        if(prec > 0)
            num *= 10.0;
        else
            num /= 10.0;
    double result = (long long)floor(num + 0.5);
    for (int i = 0; i < abs(prec); ++i)
        if(prec > 0)
            result /= 10.0;
        else
            result *= 10.0;
    return result;
}

2

double round_double(double num, int prec)
{
    double tmp = pow(10.0, prec);
    double result = (long long)floor(num * tmp + 0.5);
    result /= tmp;
    return result;
}

这个功能做了我想做的事,但在我看来,它们还不够好。因为从精确度= 13 - 14开始,它们会返回不良结果。

原因我确信有可能编写好的double_round只是通过cout以指定的精度(比如18)打印数字,打印效果比我的函数效果好。

例如这部分代码:

int prec = 18;
double num = 10.123456789987654321;
cout << setiosflags(ios::showpoint | ios::fixed)
<< setprecision(prec) << "round_double(" << num << ", " 
<< prec << ") = " << round_double(num, prec) << endl;

将第一个round_double(10.123456789987655000, 18) = -9.223372036854776500打印round_double,第二个打印round_double(10.123456789987655000, 18) = -9.223372036854776500

如何在c ++中编写好的round_double函数?或者已经存在?

3 个答案:

答案 0 :(得分:2)

不要强制转换为强制转换为范围有限的整数的long long,超过10 ^ 13要求的范围(对于64位而没有整数部分,则为19)。只需拨打floor即可。

double round_double(double num, int prec)
{
    double tmp = pow(10.0, prec);
    double result = floor(num * tmp + 0.5);
    result /= tmp;
    return result;
}

请注意,迈克也是正确的,你有一个有限的范围,你可以代表双重本身。如果你需要干净的十进制响应,那就没那么好了。但是long long是你完全古怪的数字的原因。

答案 1 :(得分:1)

问题是浮点表示。二进制表示不能精确地表示所有十进制数,并且只有有限的精度。

double通常表示由IEEE754指定的64位二进制表示,具有52位小数部分。这给出了大约16位十进制数的精度。

如果你需要更高的精度,那么最好的选择可能是使用任意精度的算术库,例如GMP。您的编译器可能提供或不提供精度高于long double的{​​{1}}类型。

编辑:抱歉,我没有注意到您的结果完全不正确。正如另一个答案所说,这是由于转换为double溢出。

答案 2 :(得分:0)

另一种方法是基于精度的二进制数进行舍入。下面的示例实现 - 不确定它是否对您有用,但是因为您让我玩,我以为我会把它扔出去。

注意:

  • 这使用了Linux系统上常见的ieee754.h标头:它可以很容易地移植到Windows上,但无可否认这有点过时,以及它是否适用于任何给定的生产代码都是个案通话。
  • 你可以近似一些近似等效的小数,例如将所需的小数精度乘以10并除以3(基于2 ^ 10~ = 10 ^ 3)。

1位精度的输入数(10.1234 ...)为8; 2,等于10等。

另外,恕我直言的十进制舍入最好在输出时或使用支持十进制的表示(例如存储int尾数和10次幂指数)时完成。

#include <ieee754.h>
#include <iostream>
#include <iomanip>

double round_double(double d, int precision)
{
    ieee754_double* p = reinterpret_cast<ieee754_double*>(&d);
    std::cout << "mantissa  0:" << std::hex << p->ieee.mantissa0
        << ", 1:" << p->ieee.mantissa1 << '\n';
    unsigned mask0 = precision < 20 ? 0x000FFFFF << (20 - precision) :
                                      0x000FFFFF;
    unsigned mask1 = precision <  20 ? 0 :
                     precision == 53 ? 0xFFFFFFFF :
                                       0xFFFFFFFE << (32 + 20 - precision);
    std::cout << "masks     0:" << mask0 << ", 1: " << mask1 << '\n';
    p->ieee.mantissa0 &= mask0;
    p->ieee.mantissa1 &= mask1;
    std::cout << "mantissa' 0:" << p->ieee.mantissa0
        << ", 1:" << p->ieee.mantissa1 << '\n';
    return d;
}

int main()
{
    double num = 10.123456789987654321;

    for (int prec = 1; prec <= 53; ++prec)
        std::cout << std::setiosflags(std::ios::showpoint | std::ios::fixed)
            << std::setprecision(60)
            << "round_double(" << num << ", "  << prec << ") = "
            << round_double(num, prec) << std::endl;
}

...输出

mantissa  0:43f35, 1:ba76eea7
masks     0:fff80000, 1: 0
mantissa' 0:0, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 1) = 8.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:fffc0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 2) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:fffe0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 3) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:ffff0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 4) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:ffff8000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 5) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:ffffc000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 6) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:ffffe000, 1: 0
mantissa' 0:42000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 7) = 10.062500000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:fffff000, 1: 0
mantissa' 0:43000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 8) = 10.093750000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:7ffff800, 1: 0
mantissa' 0:43800, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 9) = 10.109375000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:3ffffc00, 1: 0
mantissa' 0:43c00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, a) = 10.117187500000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:1ffffe00, 1: 0
mantissa' 0:43e00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, b) = 10.121093750000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:fffff00, 1: 0
mantissa' 0:43f00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, c) = 10.123046875000000000000000000000000000000000000000000000000000

etc....