截断一定数字位数的双浮点

时间:2016-12-23 02:18:33

标签: c++ rounding truncation

我编写了以下例程,它应该在第n个小数位截断C ++ double。

double truncate(double number_val, int n)
{
    double factor = 1;
    double previous = std::trunc(number_val); // remove integer portion
    number_val -= previous;
    for (int i = 0; i < n; i++) {
        number_val *= 10;
        factor *= 10;
    }
    number_val = std::trunc(number_val);
    number_val /= factor;
    number_val += previous; // add back integer portion
    return number_val;
}

通常情况下,这很有效......但是我发现有些数字,特别是那些似乎没有精确表示的数字,有问题。

例如,如果输入是2.0029,并且我想在第五位截断它,在内部,double似乎存储在2.0028999999999999996和2.0028999999999999999之间的某个位置,并且在第五个小数位截断它给出2.00289,这可能是正确的数字存储方式,但对最终用户来说似乎是错误的答案。

如果我在四舍五入而不是在第五个十进制处截断,那么一切都会好的,当然,如果我给出一个双十进制表示超过小数点后n位数的双精度,它也可以正常工作,但是怎么做我修改了这个截断例程,以便由于double类型及其十进制表示中的不精确而导致的不准确性不会影响最终用户看到的结果?

我想我可能需要某种舍入/截断混合来实现这项工作,但我不确定如何编写它。

编辑:感谢到目前为止的回复,但也许我应该澄清这个值不是必然产生输出,但是这个截断操作可以是浮点数上许多不同用户指定操作的链的一部分。在多个操作中在双精度内累积的误差很好,但是没有单个操作(例如截断或舍入)应该产生的结果与其实际理想值相差超过epsilon的一半,其中epsilon是表示的最小幅度通过双精度与当前指数。我目前正试图消化下面关于浮点运算的iinspectable提供的链接,看看它是否会帮我弄清楚如何做到这一点。

编辑:这个链接给了我一个想法,这有点像hacky但它​​应该可以工作,就是在我开始用它做任何其他事情之前将number_val += std::numeric_limits<double>::epsilon()这样的行放在函数的顶部。不过,如果有更好的方法,不知道。

编辑:今天我在公共汽车上时有一个想法,我还没有机会彻底测试,但它的工作原理是将原始数字四舍五入到16位有效小数位,然后将其截断:

double truncate(double number_val, int n)
{
    bool negative = false;
    if (number_val == 0) {
        return 0;
    } else if (number_val < 0) {
        number_val = -number_val;
        negative = true;
    } 
    int pre_digits = std::log10(number_val) + 1;
    if (pre_digits < 17) {
        int post_digits = 17 - pre_digits;
        double factor = std::pow(10, post_digits);
        number_val = std::round(number_val * factor) / factor;
        factor = std::pow(10, n);
        number_val = std::trunc(number_val * factor) / factor;
    } else {
        number_val = std::round(number_val);
    }
    if (negative) {
        number_val = -number_val;
    }
    return number_val;
}

由于双精度浮点数无论如何只能有大约16位数的精度,所以这可能适用于所有实际用途,最多只需要一倍的精度,否则双倍可能支持。

我想进一步指出,这个问题与上面建议的副本的不同之处在于a)这是使用C ++,而不是Java ...我没有DecimalFormatter便利类,而b)我是想要截断,而不是舍入给定数字的数字(在double数据类型允许的精度限制内),以及c)如前所述,此函数的结果是假设是一个可打印的字符串...它应该是一个本机浮点数,该函数的最终用户可能会选择进一步操作。由于double类型的不精确而导致多次操作的累积错误是可以接受的,但是任何单个操作都应该正确地执行到double数据类型的精度限制。

3 个答案:

答案 0 :(得分:0)

好的,如果我理解这一点,你有一个浮点数,你想把它截断为n位数:

10.099999
   ^^      n = 2

becomes

10.09
   ^^

但是你的功能是将数字截断为近似接近的值:

10.08999999
   ^^

然后显示为10.08

如何保留truncate公式,尽可能截断公式,并使用std::setprecisionstd::fixed将截断值四舍五入到所需的小数位数? (假设您std::cout用于输出?)

#include <iostream>
#include <iomanip>

using std::cout;
using std::setprecision;
using std::fixed;
using std::endl;

int main() {
  double foo = 10.08995; // let's imagine this is the output of `truncate`

  cout << foo << endl;                             // displays 10.0899
  cout << setprecision(2) << fixed << foo << endl; // rounds to 10.09
}

我为wandbox设置了一个演示版。

答案 1 :(得分:0)

我已经调查过了。这很难,因为浮点表示会导致不准确,然后由于小数而导致进一步的不准确。 0.1不能用二进制浮点精确表示。但是,您可以使用带有%g参数的内置函数sprintf,该参数应该为您精确舍入。

 char out[64];
 double x = 0.11111111;
 int n = 3;
 double xrounded;
 sprintf(out, "%.*g", n, x);
 xrounded = strtod(out, 0);

答案 2 :(得分:0)

获取双精度字符串

如果您只是想打印输出,那么使用 stringstream 非常简单直接:

#include <cmath>
#include <iostream>
#include <iomanip>
#include <limits>
#include <sstream>

using namespace std;

string truncateAsString(double n, int precision) {
    stringstream ss;
    double remainder = static_cast<double>((int)floor((n - floor(n)) * precision) % precision);
    ss << setprecision(numeric_limits<double> ::max_digits10 + __builtin_ctz(precision))<< floor(n);
    if (remainder)
        ss << "." << remainder;
    cout << ss.str() << endl;
    return ss.str();
}

int main(void) {
    double a = 9636346.59235;
    int precision = 1000; // as many digits as you add zeroes. 3 zeroes means precision of 3.
    string s = truncateAsString(a, precision);
    return 0;
}

得到一个精确值的除法浮点

也许您正在寻找浮点数的真实值,您可以使用 boost multiprecision library

<块引用>

Boost.Multiprecision 库可用于需要精度超过标准内置类型(如 float、double 和 long double)的计算。对于扩展精度计算,Boost.Multiprecision 提供了一个名为 cpp_dec_float 的模板数据类型。精度的十进制位数在编译时通过模板参数固定。

演示

#include <boost/math/constants/constants.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>
#include <limits>
#include <cmath>
#include <iomanip>

using boost::multiprecision::cpp_dec_float_50;

cpp_dec_float_50 truncate(cpp_dec_float_50 n, int precision) {
    cpp_dec_float_50 remainder = static_cast<cpp_dec_float_50>((int)floor((n - floor(n)) * precision) % precision) / static_cast<cpp_dec_float_50>(precision);
    return floor(n) + remainder;
}

int main(void) {
    int precision = 100000; // as many digits as you add zeroes. 5 zeroes means precision of 5.
    cpp_dec_float_50 n = 9636346.59235789;
    n = truncate(n, precision); // first part is remainder, floor(n) is int value truncated.
    cout << setprecision(numeric_limits<cpp_dec_float_50> ::max_digits10 + __builtin_ctz(precision)) << n << endl; // __builtin_ctz(precision) will equal the number of trailing 0, exactly the precision we need!
    return 0;
}

输出:

9636346.59235

注意:需要 sudo apt-get install libboost-all-dev