標準ライブラリの実装を使用することほど洗練されたものはありません。エレガントなコードほど常にエレガントなコードはありません。
それはさておき、このアプローチには 2 つの大きな欠点があります。
X
が より大きいINT_MAX + 1
か小さい場合、INT_MIN - 1
マクロの動作は未定義です。これは、実装によって、すべての浮動小数点数のほぼ半分に対して誤った結果が得られる可能性があることを意味します。また、IEEE-754 とは異なり、無効フラグも発生します。
- -0、+/-infinity、および nan のエッジ ケースが間違っています。実際、それが正しくなる唯一のエッジケースは +0 です。
次のように、試したのと同様の方法で実装できます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);
}
そのようなものは、あなたが得ることができるのと同じくらいエレガントであり、それでも正しいです.
私は、Martin が彼の回答に入れた (一般的には良い!) 実装に関する多くの懸念にフラグを立てました。彼のアプローチを実装する方法は次のとおりです。
#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);
}
このアプローチについて注意すべきことの 1 つは、非整数入力に対して不正確な浮動小数点フラグが発生しないことです。それはあなたの使用法にとって問題になるかもしれませんし、そうでないかもしれません。私がリストした最初の実装はフラグを立てます。