Ceil函数:我们如何自己实现它?

22

我知道C++为我们提供了一个ceil函数。出于练习的目的,我想知道如何在C ++中实现ceil函数。该方法的签名为public static int ceil(float num)。

请提供一些见解。

我想到了一个简单的方法:将num转换为字符串,找到小数点的索引,检查小数部分是否大于0。如果是,则返回num + 1,否则返回num。但我想避免使用字符串转换。


7
std::ceil()的签名不是public static int ceil(float)。请注意,Java或C#的参考资料可能在快速浏览时看起来像C++。始终确保所咨询的参考资料是针对C++的。 - André Caron
10个回答

33

您可以将IEEE754浮点数的各个组成部分拆开,并自行实现逻辑:

#include <cstring>

float my_ceil(float f)
{
    unsigned input;
    memcpy(&input, &f, 4);
    int exponent = ((input >> 23) & 255) - 127;
    if (exponent < 0) return (f > 0);
    // small numbers get rounded to 0 or 1, depending on their sign

    int fractional_bits = 23 - exponent;
    if (fractional_bits <= 0) return f;
    // numbers without fractional bits are mapped to themselves

    unsigned integral_mask = 0xffffffff << fractional_bits;
    unsigned output = input & integral_mask;
    // round the number down by masking out the fractional bits

    memcpy(&f, &output, 4);
    if (f > 0 && output != input) ++f;
    // positive numbers need to be rounded up, not down

    return f;
}

(在此插入通常的“不可移植”免责声明。)


你可以使用frexpldexp来进行提取和重新组合。 - Oliver Charlesworth
1
@OliCharlesworth:frexp返回一个浮点数,而不是整数。那么你打算如何屏蔽掉小数位呢? - fredoverflow
2
难以置信你的回答没有被采纳(更不用说被采纳的回答是错误的)! - BeyelerStudios

17

这是一个针对正数的简单实现(该实现利用了将数字转换为(int)时向零截断的特性):

int ceil(float num) {
    int inum = (int)num;
    if (num == (float)inum) {
        return inum;
    }
    return inum + 1;
}

很容易将此扩展以处理负数。

您的问题要求返回int类型的函数,但通常ceil()函数返回与其参数相同的类型,因此范围没有问题(即float ceil(float num))。例如,如果num为1e20,则上述函数会失败。


3
考虑到大多数整数在流行的 float 实现中无法准确表示,我怀疑这不是一种可靠的实现 ceil() 的方式。 - André Caron
3
@AndréCaron:这是正确的,但对于这个特定的实现来说不应该是问题,因为函数的输入不是一个整数。 - Greg Hewgill
我正在寻找像你的答案一样简单的东西。 - TimeToCodeTheRoad
4
仅当数字为正数时,才应执行 inum + 1 操作。 - Jarod42

7
那基本上就是你需要做的,但不需要转换成字符串。
浮点数表示为(+/-) M * 2^E。指数E告诉您距离二进制点有多远*。如果E足够大,则没有小数部分,因此不需要进行任何操作。如果E足够小,则没有整数部分,因此答案为1(假设M非零且数字为正)。否则,E告诉您二进制点出现在您的尾数中的位置,您可以使用它来进行检查,然后执行舍入。 * 不是十进制点,因为我们处于基于2而不是基于10的情况下。

@Charlesworth:是的,我知道这个事实。我更关心的是如何利用它来确定小数部分? - TimeToCodeTheRoad
2
@时间:您可以使用frexp()函数提取ME。然后,您可以使用E来确定M中有多少位数字在二进制点上方,以及有多少位数字在下方。 - Oliver Charlesworth

5

我的看法:

template <typename F>
inline auto ceil(F const f) noexcept
{
  auto const t(std::trunc(f));

  return t + (t < f);
}

1
除了这不是“constexpr”。 - Anton3
@Anton3 谢谢,如果你自己实现 trunc() 的话,就可以这样做。 - user1095108
如果您不关心精度,可以执行const int t = int(f); - Volper
哦不,你不能这样做,f 可以是一个天文数字。 - user1095108

1

之前的代码建议:

int ceil(float val) 
{
    int temp  = val * 10;
    if(val%10)
    return (temp+1);
    else
    return temp;
}

无法编译:在第四行“if(val%10)”上收到“错误C2296:'%': 非法的左操作数类型为'float'”,因为不能在浮点数或双精度数上使用模运算符(%)。请参见:为什么我们不能在浮点数和双精度数类型操作数上使用运算符%? 此外,对于精度不大于1/10的十进制值,它也无法工作。

相比之下,先前的代码建议:

int ma_ceil(float num)
{   int a = num;
    if ((float)a != num)
        return num+1;
    return num;
}

只要不超出浮点值的范围,它就能正常工作。 num = 555555555; 或者 num = -5.000000001 除非使用double,否则无法工作。
此外,由于浮点数和双精度浮点数存储在IEEE浮点格式中,所以存储的二进制表示可能是不精确的。例如:
float num = 5; 在某些情况下可能不会被赋值为5.0000000,而是5.9999998或5.00000001。 为了纠正之前的代码版本,建议将返回值更改为使用整数运算,而不是依赖浮点值的精度,如下:
int ma_ceil(float num)
{   int a = num;
    if ((float)a != num)
        return a+1;
    return a;
}

0

就像这样(纯粹从第一原理出发):

#include <iostream>
#include <type_traits>


template<typename T>
constexpr int truncate( const T val )
{
    return static_cast<int>( val );
}

template<typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
constexpr T modulusFloat( const T divident,
    const T divisor )
{
    return divident - truncate( divident / divisor ) * divisor;
}

template<typename T>
constexpr T modulus( const T divident,
    const T divisor ) noexcept
{
    if constexpr ( std::is_floating_point_v<T> )
    {
        return modulusFloat( divident,
            divisor );
    }
    else
    {
        return divident % divisor;
    }
}

template<typename T>
constexpr T ceil( const T val ) noexcept
{
    return val + (T)1 - modulus( val, (T)1 );
}

int main()
{
    std::cout << ceil( 5.4 ) << '\n';
    std::cout << ceil( -5.4 ) << '\n';

    return 0;
}

0

类似这样:

  double param, fractpart, intpart;

  param = 3.14159265;
  fractpart = modf (param , &intpart);

  int intv = static_cast<int>(intpart); // can overflow - so handle that.

  if (fractpart > some_epsilon)
    ++intv;

您只需要定义some_epsilon值,使得在整数部分递增之前,小数部分比之前更大。其他需要考虑的因素包括符号(即值是否为负等)。


我想你希望 epsilon 的值为0。 - Oliver Charlesworth
@OliCharlesworth,真的吗?难道不应该是尽可能接近0的值(比如说1E-6),以允许表示问题吗? - Nim
这不是 std::numeric_limits<float>::epsilon() 的用途吗? - Violet Giraffe

0

这是一个可以处理负数的代码:

int ceil(float num) {
    int inum = (int)num;
    if (num < 0 || num == (float)inum) {
        return inum;
    }
    return inum + 1;
}

1
你好@aqeel,欢迎来到StackOverflow。你是否查看了如何回答的指南?它能帮助你避免回答被下投票。而且,为什么不提供一些关于代码片段如何解决所述问题的详细信息呢? - ndrwnaguib

-1

它也适用于负值

int ma_ceil(float num)
{   int a = num;
    if ((float)a != num)
        return num+1;

    return num;
}

-5

试试这个...

int ceil(float val)
{
    int temp  = val * 10;
    if(val%10)
    return (temp+1);
    else
    return temp;
}

需要调整为负值。 - Oliver Charlesworth
4
еРМжЧґпЉМеЃГдЄНе§ДзРЖдЊЛе¶Вval = 0.01зЪДжГЕеЖµгАВ - Oliver Charlesworth
@Oli 如果您想要检查到小数点后两位,请将val乘以100。 - Veer Bahadur Singh
3
好的,那么它就无法处理“0.001”。 - R. Martinho Fernandes
5
但这并不是“实现ceil”问题的解决方案。 - R. Martinho Fernandes
显示剩余2条评论

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