将一个浮点数四舍五入到一个预定义点的规则网格上。

32

我想将一个浮点数四舍五入到给定的精度,例如:

0.051 i want to convert it to
0.1

0.049 i want to convert it to
0.0

0.56 i want to convert it to
0.6

0.54 i want to convert it to
0.5

我无法更好地解释,但这样做的原因是将点位置(例如0.131f、0.432f)转换为网格中瓦片的位置(例如0.1f、0.4f)。


1
将浮点数四舍五入到1个或更多小数位并没有太多意义;像0.1这样的数字在二进制浮点数中无法精确表示。四舍五入可以在输出时进行(转换为字符串或写入文件)。 - Keith Thompson
我们在IT部门,试图用代码行来表示现实世界的无限可能性,这里的一切都很有意义。我正在将其用于游戏中的无限滚动背景,失去精度并不重要。 - SteveL
@BlunT,是的,标题似乎有误导性,但当时我找不到更好的标题。 - SteveL
@royv(如果您在这里),这个问题不是您问题的副本,我建议您取消删除您的问题。这个问题实际上修改了浮点数并且也没有解决显示问题。解决显示问题是另一回事,不需要修改浮点数。 - M.M
在程序中对数字进行四舍五入确实是有意义的,如果您失去精度并需要将结果与其他内容进行比较。例如,如果您将矩阵(内部为双精度数组)乘以其倒数,您应该得到单位矩阵,但由于在乘法例程内可能会失去精度,因此您可能会在应该得到零的位置得到小数字。如果您尝试将结果与真实的单位矩阵进行比较,则会得到FALSE而不是预期的TRUE。因此,在这种情况下,舍入乘法结果可能是一个好主意。我的舍入方法:#define ROUND(n,p) round(n*pow(10,p))/pow(10,p) - Vinícius A. Jorge
显示剩余3条评论
11个回答

26
只要你的网格是规则的,只需找到从整数到该网格的转换。假设你的网格是:
0.2  0.4  0.6  ...

然后你通过四舍五入

float round(float f)
{
    return floor(f * 5 + 0.5) / 5;
    // return std::round(f * 5) / 5; // C++11
}

似乎无法正常工作。0.5 被转换为 0.6。 - Niki
3
“@Niki ……这正是你所期望的,不是吗?” - marton78
不完全是马尔顿,0.5已经满足条件了,也许应该保持原样。 - SexyBeast
4
哪个条件?就像我写的那样,“假设你的网格是0.2 0.4 0.6…” - marton78
如果这个数字是负数,你需要减去0.5而不是加上0.5。 - Omtara

9
标准的ceil()floor()函数没有精度,我猜可以通过添加自己的精度来解决这个问题,但这可能会引入误差,例如:
double ceil(double v, int p)
{
  v *= pow(10, p);
  v = ceil(v);
  v /= pow(10, p);
}

我猜您可以测试一下,看看这对您是否可靠?

这不是等于 /= pow(10, p) 或者 *= pow(10, -p) 吗? - Mmmh mmh

7

编辑 1:我在寻找Python中Numpy的解决方案,并没有意识到OP要求的是C ++哈哈,算了。

编辑 2:哈哈,看起来我甚至没有回答你最初的问题。看起来你真的想按小数四舍五入(操作与给定数字无关),而不是按精度四舍五入(操作取决于数字),其他人已经解决了这个问题。

实际上,我也在寻找这个东西,但找不到,所以我为Numpy数组编写了一个实现。看起来它实现了slashmais所述的逻辑。

def pround(x, precision = 5):
    temp = array(x)
    ignore = (temp == 0.)
    use = logical_not(ignore)
    ex = floor(log10(abs(temp[use]))) - precision + 1
    div = 10**ex
    temp[use] = floor(temp[use] / div + 0.5) * div
    return temp

这里还有一个C++标量版本,你也可以使用Eigen做类似上面的操作(它们有逻辑索引):(我也趁此机会练习了一些boost库哈哈):

#include <cmath>
#include <iostream>
#include <vector>
#include <boost/foreach.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

using namespace std;

double pround(double x, int precision)
{
    if (x == 0.)
        return x;
    int ex = floor(log10(abs(x))) - precision + 1;
    double div = pow(10, ex);
    return floor(x / div + 0.5) * div;
}

    template<typename T>
vector<T>& operator<<(vector<T> &x, const T &item)
{
    x.push_back(item);
    return x;
}

int main()
{
    vector<double> list;
    list << 0.051 << 0.049 << 0.56 << 0.54;
    // What the OP was wanting
    BOOST_FOREACH(double x, list)
    {
        cout << floor(x * 10 + 0.5) / 10 << "\n";
    }

    cout << "\n";

    BOOST_FOREACH(double x, list)
    {
        cout << pround(x, 0) << "\n";
    }

    cout << "\n";

    boost::function<double(double)> rounder = boost::bind(&pround, _1, 3);
    vector<double> newList;
    newList << 1.2345 << 1034324.23 << 0.0092320985;
    BOOST_FOREACH(double x, newList)
    {
        cout << rounder(x) << "\n";
    }

    return 0;
}

输出:

0.1
0
0.6
0.5

0.1
0
1
1

1.23
1.03e+06
0.00923

6

一种算法:

  • 获取10的(有效数字数)次方(=P10)
  • 将您的双精度值乘以P10
  • 添加:0.5(如果为负数则减去-请参见Ankush Shah的评论)
  • 将此和的整数部分除以(P10),答案将是您四舍五入后的数字

2
小提示:如果这个数字是负数,你需要减去0.5。 - Ankush Shah

4

使用floor()ceil()函数。 floor将把一个浮点数转换为下一个较小的整数,而ceil则转换为下一个较大的整数:

floor( 4.5 ); // returns 4.0
ceil( 4.5 );  // returns 5.0

我认为以下内容可行:
float round( float f )
{   
    return floor((f * 10 ) + 0.5) / 10;
}

floor( f + 0.5 )会四舍五入到一个整数。通过先乘以10,然后将结果除以10,您可以按0.1的增量进行四舍五入。


负浮点数怎么办?(在标准库更好的覆盖之前,我曾经用它来处理正浮点数。) - sage

3

通常你在编译时就知道所需的精度。因此,可以使用这里提供的模板化Pow函数进行以下操作:

template <int PRECISION>
float roundP(float f)
{
    const int temp = Pow<10,PRECISION>::result;
    return roundf(f*temp)/temp;
}

int main () {
    std::cout << std::setprecision(10);
    std::cout << roundP<0>(M_PI) << std::endl;
    std::cout << roundP<1>(M_PI) << std::endl;
    std::cout << roundP<2>(M_PI) << std::endl;
    std::cout << roundP<3>(M_PI) << std::endl;
    std::cout << roundP<4>(M_PI) << std::endl;
    std::cout << roundP<5>(M_PI) << std::endl;
    std::cout << roundP<6>(M_PI) << std::endl;
    std::cout << roundP<7>(M_PI) << std::endl;
}

这里进行了测试。

结果还显示了浮点数表示的不精确性 :)

3

3.099999905

3.140000105

3.14199996

3.141599894

3.141590118

3.141592979

3.141592741

使用double可以得到更好的结果:

template <int PRECISION>
double roundP(double f)
{
    const int temp = Pow<10,PRECISION>::result;
    return round(f*temp)/temp;
}

精度为20的打印结果:

3

3.1000000000000000888

3.1400000000000001243

3.1419999999999999041

3.1415999999999999481

3.1415899999999998826

3.1415929999999998579

3.1415926999999999047


3

我将简要优化一下之前的答案,先将输入数字转换为双精度浮点数以防止溢出。以下是一个示例函数(不太美观,但完全可行):

#include <cmath>

// round float to n decimals precision
float round_n (float num, int dec)
{
    double m = (num < 0.0) ? -1.0 : 1.0;   // check if input is negative
    double pwr = pow(10, dec);
    return float(floor((double)num * m * pwr + 0.5) / pwr) * m;
}

2
您可以使用以下函数将数字舍入到所需的精度:
double round(long double number, int precision) {
  int decimals = std::pow(10, precision);
  return (std::round(number * decimals)) / decimals;
}

Check some examples below...

1)

round(5.252, 0)
returns => 5

2)

round(5.252, 1)
returns => 5.3

3)

round(5.252, 2)
returns => 5.25

4)

round(5.252, 3)
returns => 5.252

这个函数甚至可以处理有9位精度的数字。 5)
round(5.1234500015, 9)
returns => 5.123450002

1

我曾经写过一个小函数,可以将double舍入到固定精度(例如2个小数位 - 如0.01),而不需要包含<cmath>

constexpr double simplyRounded( const double value )
{
    return (((((int)(value * 1000.0) % 10) >= 5) + ((int)(value * 100.0))) / 100.0);
}   

...这可以被调整以给您一个小数点后1位的精度(例如0.1),接受并返回float,如下所示:

constexpr float simplyRounded( const float value )
{
    return (((((int)(value * 1000.0f) % 10) >= 5) + ((int)(value * 100.0f))) / 100.0f);
}


0

由于 Mooing Duck 编辑了我的问题并删除了代码,说问题不应该包含答案(可以理解),所以我将在这里写出解决方案:

float round(float f,float prec)
{
    return (float) (floor(f*(1.0f/prec) + 0.5)/(1.0f/prec));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接