繁体   English   中英

在一定位数处截断双浮点

[英]Truncating a double floating point at a certain number of digits

我编写了以下例程,它应该在小数点后第 n 位截断 C++ 双精度。

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;
}

通常,这很好用......但我发现有些数字,尤其是那些似乎在 double 中没有精确表示的数字,有问题。

例如,如果输入是 2.0029,并且我想在第五位截断它,在内部,双精度似乎存储为 2.0028999999999999996 和 2.00289999999999999999 之间的某个值,并且在小数点后第五位截断它会得到 2.00289,这可能是就数字的存储方式而言是正确的,但对于最终用户来说,这看起来像是错误的答案。

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

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

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

编辑:好吧,链接给了我一个想法,这有点骇人听闻,但它应该可以工作,即在函数的顶部放置一个像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) 正如我之前所说,此函数的结果应该是可打印的字符串......它应该是此函数的最终用户可能选择进一步操作的本机浮点数。 由于双精度类型的不精度导致的多个操作的累积错误是可以接受的,但任何单个操作都应该在双精度数据类型的精度限制范围内正确执行。

好的,如果我理解正确,您有一个浮点数,并且您想将其截断为 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上设置了一个演示。

我已经调查过了。 这很难,因为浮点表示不准确,然后由于小数而进一步不准确。 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);

获取 double 作为字符串

如果您只想打印输出,那么使用 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 多精度库

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM