我想用C实现我自己的ceil()。在库中搜索源代码并找到了here,但它似乎很难理解。我想要干净和优雅的代码。
我也在SO上搜索了一下,找到了一些答案here。似乎没有一个答案是正确的。其中一个答案是:
#define CEILING_POS(X) ((X-(int)(X)) > 0 ? (int)(X+1) : (int)(X))
#define CEILING_NEG(X) ((X-(int)(X)) < 0 ? (int)(X-1) : (int)(X))
#define CEILING(X) ( ((X) > 0) ? CEILING_POS(X) : CEILING_NEG(X) )AFAIK,ceil()的返回类型不是int。宏在这里是类型安全的吗?此外,上面的实现对负数有效吗?
实现它的最佳方式是什么?
你能提供干净的代码吗?
发布于 2012-09-05 19:04:04
对于大于INT_MAX的数字,您引用的宏肯定不能正确工作,但仍然可以精确地表示为双精度数。
正确实现ceil()的唯一方法(假设您不能使用等效的汇编指令来实现它)是对浮点数的二进制表示进行位旋转,就像在第一个链接后面的s_ceil.c源文件中所做的那样。要理解代码的工作原理,需要理解底层平台的浮点表示法--表示法很可能是IEEE 754 --但这是没有办法的。
编辑:
s_ceil.c中的一些复杂性源于它处理的特殊情况(NaN、无穷大),以及它需要在不能假设存在64位整数类型的情况下完成工作的事实。
所有位旋转的基本思想是屏蔽尾数的小数位,如果尾数大于零,则将其加1...但是,为了确保您在所有情况下都做正确的事情,还涉及到一些额外的逻辑。
这是我拼凑而成的浮点数的ceil()的一个说明性版本。注意:这不能正确处理特殊情况,也没有经过广泛的测试--所以不要实际使用它。然而,它确实有助于说明在比特摆弄中涉及的原则。我已经尝试对该例程进行了广泛的注释,但注释确实假设您了解如何在IEEE754格式中表示浮点数。
union float_int
{
float f;
int i;
};
float myceil(float x)
{
float_int val;
val.f=x;
// Extract sign, exponent and mantissa
// Bias is removed from exponent
int sign=val.i >> 31;
int exponent=((val.i & 0x7fffffff) >> 23) - 127;
int mantissa=val.i & 0x7fffff;
// Is the exponent less than zero?
if(exponent<0)
{
// In this case, x is in the open interval (-1, 1)
if(x<=0.0f)
return 0.0f;
else
return 1.0f;
}
else
{
// Construct a bit mask that will mask off the
// fractional part of the mantissa
int mask=0x7fffff >> exponent;
// Is x already an integer (i.e. are all the
// fractional bits zero?)
if((mantissa & mask) == 0)
return x;
else
{
// If x is positive, we need to add 1 to it
// before clearing the fractional bits
if(!sign)
{
mantissa+=1 << (23-exponent);
// Did the mantissa overflow?
if(mantissa & 0x800000)
{
// The mantissa can only overflow if all the
// integer bits were previously 1 -- so we can
// just clear out the mantissa and increment
// the exponent
mantissa=0;
exponent++;
}
}
// Clear the fractional bits
mantissa&=~mask;
}
}
// Put sign, exponent and mantissa together again
val.i=(sign << 31) | ((exponent+127) << 23) | mantissa;
return val.f;
}发布于 2012-09-05 19:06:22
没有什么比使用标准库实现更优雅的了。没有代码总是比优雅的代码更优雅。
除此之外,这种方法有两个主要缺陷:
X大于INT_MAX + 1或小于INT_MIN - 1,则宏的行为未定义。这意味着您的实现可能会给出几乎一半的浮点数的错误结果。您还将引发无效标志,这与IEEE-754相反。您可以使用类似于您尝试的方式来实现ceil,如下所示(此实现假设IEEE-754双精度):
#include <math.h>
double ceil(double x) {
// All floating-point numbers larger than 2^52 are exact integers, so we
// simply return x for those inputs. We also handle ceil(nan) = nan here.
if (isnan(x) || fabs(x) >= 0x1.0p52) return x;
// Now we know that |x| < 2^52, and therefore we can use conversion to
// long long to force truncation of x without risking undefined behavior.
const double truncation = (long long)x;
// If the truncation of x is smaller than x, then it is one less than the
// desired result. If it is greater than or equal to x, it is the result.
// Adding one cannot produce a rounding error because `truncation` is an
// integer smaller than 2^52.
const double ceiling = truncation + (truncation < x);
// Finally, we need to patch up one more thing; the standard specifies that
// ceil(-small) be -0.0, whereas we will have 0.0 right now. To handle this
// correctly, we apply the sign of x to the result.
return copysign(ceiling, x);
}像这样的东西是你能得到的最优雅的东西,而且仍然是正确的。
我指出了一些问题(总体上是好的!)马丁在他的答案中提出的实现。下面是我将如何实现他的方法:
#include <stdint.h>
#include <string.h>
static inline uint64_t toRep(double x) {
uint64_t r;
memcpy(&r, &x, sizeof x);
return r;
}
static inline double fromRep(uint64_t r) {
double x;
memcpy(&x, &r, sizeof x);
return x;
}
double ceil(double x) {
const uint64_t signbitMask = UINT64_C(0x8000000000000000);
const uint64_t significandMask = UINT64_C(0x000fffffffffffff);
const uint64_t xrep = toRep(x);
const uint64_t xabs = xrep & signbitMask;
// If |x| is larger than 2^52 or x is NaN, the result is just x.
if (xabs >= toRep(0x1.0p52)) return x;
if (xabs < toRep(1.0)) {
// If x is in (1.0, 0.0], the result is copysign(0.0, x).
// We can generate this value by clearing everything except the signbit.
if (x <= 0.0) return fromRep(xrep & signbitMask);
// Otherwise x is in (0.0, 1.0), and the result is 1.0.
else return 1.0;
}
// Now we know that the exponent of x is strictly in the range [0, 51],
// which means that x contains both integral and fractional bits. We
// generate a mask covering the fractional bits.
const int exponent = xabs >> 52;
const uint64_t fractionalBits = significandMask >> exponent;
// If x is negative, we want to truncate, so we simply mask off the
// fractional bits.
if (xrep & signbitMask) return fromRep(xrep & ~fractionalBits);
// x is positive; to force rounding to go away from zero, we first *add*
// the fractionalBits to x, then truncate the result. The add may
// overflow the significand into the exponent, but this produces the
// desired result (zero significand, incremented exponent), so we just
// let it happen.
return fromRep(xrep + fractionalBits & ~fractionalBits);
}关于这种方法需要注意的一件事是,它不会为非整数输入引发不精确的浮点标志。这可能与您的使用有关,也可能与您的使用无关。我列出的第一个实现确实提升了这一标志。
发布于 2012-09-05 19:01:28
我不认为宏函数是一个很好的解决方案:它不是类型安全的,而且存在参数(副作用)的多重评估。你应该写一个干净优雅的函数。
https://stackoverflow.com/questions/12279914
复制相似问题