浮動小数点数が整数であるかどうかを調べたいことがあった。
で。
いくつか方法があるので紹介する。
ruby
to_i を使う
こんな感じ。
def integer?(x)
  x==x.to_i;
end
なお。Integer に変換できない値を入力すると例外になる。
こんな感じ。
(Float::INFINITY).to_i #=> FloatDomainError: Infinity
(Float::NAN).to_i #=> FloatDomainError: NaN
それと。to_i メソッドは Rational や Integer にも生えているのでそういう型の値を上記の関数に食べさせてもちゃんと動く。
あと。
ここでは to_i を使ったけど、ceil, round, truncate でも同じ結果になる。
ちなみに。
C++の std::floor(double) は double を返すけど、ruby の Float#floor は、Integer を返す。要注意。
実行速度も測ってみたけど、大差ない感じ。ソースコードを見ると to_i が速そうではある。
ちなみに to_i の実装はこんな風になっているらしい:
static VALUE
flo_to_i(VALUE num)
{
    double f = RFLOAT_VALUE(num);
    if (f > 0.0) f = floor(f);
    if (f < 0.0) f = ceil(f);
    return dbl2ival(f);
}
なお。
ruby の Integer は多倍長整数を扱えるので、前述の integer? は入力がひどく大きな値でも trueを返す。
そういうのが困る方のために次の節。
例えば 32bit 符号あり整数になるかどうかが知りたい場合
32bit じゃなくてもいいんだけど、そういう固定長の整数と等しい値かどうかが知りたいこともある。
そういうときにどうするか。
比較する
前述の integer? があるんだから、大小比較すればよいというアイディア。
こんな感じ。
def is_int32_1(x)
  integer?(x) && (-2**31)<=x && x<=(2**31-1)
end
なんか 2**31 とか書くのがちょっと気分悪い。そういう方のために次の節。
pack / unpack を使う
def is_int32_2(x)
  x==[x].pack('l').unpack1('l')[0]
end
なんかこれはこれで迂遠な感じだけど、どうだろう。
他にいい方法があるかなぁ。
実行結果
ここまで紹介した3つの方法の実行結果は下表の通り:
| 入力 | to_i | int32(比較) | int32(pack/unpack) | 
|---|---|---|---|
| 0.0 | true | true | true | 
| 1.0 | true | true | true | 
| 1.5 | false | false | false | 
| 2e19 | true | false | false | 
| 9e19/7 | true | false | false | 
| NAN | 例外 | 例外 | 例外 | 
| INF | 例外 | 例外 | 例外 | 
| -0.0 | true | true | true | 
| 1.5r | false | false | false | 
| 1r | true | true | true | 
| 9e19.to_r/7 | false | false | false | 
いずれの実装も非数や無限が例外になる。
それと。integer?(9e19/7) が true になるのは Float の仮数部が 53bit しかないから。
C++ / C
まあ C でも C++ でも似たような感じになるけど、とりあえず C++ で書くことにする。
std::floor を使う
こんな感じ。
# include <cmath>
bool is_integer( double x ){
  return std::floor(x)==x;
}
std::floor が返すのは double ( 入力が float なら float、long double なら long double) なので、is_integer(1e100) は true になることに注意。
それと。
上記の例では std::floor を使ったけど、std::ceil, std::round, std::trunc, std::rint, std:: nearbyint でも同様。
しかしやたらたくさんある。
std::modf を使う
std::modf を使うという手もある。
こんな感じ。
bool is_integer_modf( double x ){
  double r;
  return 0==std::modf(x, &r);
}
手元の処理系では、std::modf(無限, 略) は 0 を返すので、is_integer_modf(無限) は true になる。
std::lround を使う
2020.6.13 追記。
以下の lround を使った実装はダメ。詳しくは コメント参照。
C++11 以降には std::lround という関数がある。std::round 等と異なり、long を返す。
これを使うと long に入る範囲内の整数かどうかを調べることができる。
こんな感じ。
# include <cmath>
bool is_int_lround(double x){
  return std::lround(x)==x;
}
なお。int とか std::int32_t とかを返す同等の関数はない模様。
例えば 32bit 符号あり整数になるかどうかが知りたい場合
2020.6.13 追記。
なんか「これしか無いと思う」とか書いちゃってるけど、以下の static_cast を使った実装未定義動作を含んでるのでダメです。
詳しくは 浮動書数点数の値を整数に変換する(C, C++) を参照のこと。
C++ の場合、32bit 整数にキャストができるので、実装は簡単。
# include <cstdint>
bool is_int32( double x ){
  return static_cast<std::int32_t>(x)==x;
}
まあこれしか無いと思う。
結果
ここまでの結果をまとめると下表のようになる。
| 入力 | floor | modf | lround | int32_t | 
|---|---|---|---|---|
| 0.0 | true | true | true | true | 
| 1.0 | true | true | true | true | 
| 1.5 | false | false | false | false | 
| 1e18 | true | true | true | false | 
| 2e19 | true | true | false | false | 
| 9e19/7 | true | true | false | false | 
| NAN | false | false | false | false | 
| INF | true | true | false | false | 
| -0.0 | true | true | true | true | 
手元の処理系では long が 64bit なので、上記の通り 1e18 を入力した場合の動作が lround 版と int32_t 版で異なる。
JavaScript
Math.floor を使う
こんな感じ。
function is_integer(x) {
  return x == Math.floor(x);
}
大きな数や 無限 を入れると true になる。
こちらも Math.ceil(), Math.round(), Math.trunc() でも同じ。
bit or 演算子を使う
bit or 演算子を使うと急に 32bit 符号あり整数に変換されるという不思議な機能があるので、これを使えばいい。
こんな感じ。
function is_int_bitor(x) {
  return x == (x|0);
}
JavaScript に慣れてないので、ソースコード的には割と気持ち悪い感じがする。慣れればそうでもないんだと思う。
例えば 32bit 符号なし整数になるかどうかが知りたい場合
ruby と C++ では符号ありだったけど、JavaScript の 32bit 符号ありはもう bit or 演算子でやってしまったので、今回は符号なし。
TypedArray を使う
実装はこんな感じ。
function is_uint32_typedarray(x) {
  return (new Uint32Array([x]))[0]==x;
}
今どき Uint32Array が使えない環境も少ないと思うけど、そういう不幸な方は次の節を。
比較する
ruby で行ったように、整数かどうか判断した上で値が uint32 の範囲に入っているかどうか調べるという手もある。
こんな感じ。
function is_uint32_compare(x) {
  return is_integer(x) && 0<=x && x<=4294967295;
}
マジカルな 4294967295 が書かれているのが気分悪い。
ちなみに (1<<32)-1 の値は 0 になる模様。
x<Math.pow(2,32) と書いたほうわかりやすいかもしれない。
結果
結果は下表の通り:
| 入力 | floor | bit or | Uint32Array | uint32(比較) | 
|---|---|---|---|---|
| 0.0 | true | true | true | true | 
| 1.0 | true | true | true | true | 
| 1.5 | false | false | false | false | 
| 1e18 | true | false | false | false | 
| 2e19 | true | false | false | false | 
| NAN | false | false | false | false | 
| INF | true | false | false | false | 
| -0.0 | true | true | true | true | 
| 2147483648 | true | false | true | true | 
| 2147483647 | true | true | true | true | 
| -2147483649 | true | false | false | false | 
| -2147483648 | true | true | false | false | 
Go
math.Floor を使う
普通に。こんな感じ。
func isInteger(x float64) bool {
	return math.Floor(x) == x
}
math.Ceil, math.Round, math.Trunc, math.RoundToEven でも同じ結果になる。
というか。RoundToEven なんてあったんだ。知らなかった。
math.Modf を使う
C++ に std::modf があったが、go には math.Modf がある。
こんな感じ。
func isIntegerModf(x float64) bool {
	_, frac := math.Modf(x)
	return frac == 0
}
C++ の(手元の処理系の) std::modf と異なり、go の math.Modf(無限) は (無限,非数) を返すので、isIntegerModf(無限) は false になる。
例えば 32bit 符号なし整数になるかどうかが知りたい場合
2021.4.26 追記
以下で「キャストすればよい」となっているが、
The Go Programming Language Specification - The Go Programming Language を見ると、浮動小数点数から整数への変換は、まず
When converting a floating-point number to an integer, the fraction is discarded (truncation towards zero).
とした上で
In all non-constant conversions involving floating-point or complex values, if the result type cannot represent the value the conversion succeeds but the result value is implementation-dependent.
となっている。つまり、
f := math.Pow(2, 33)
v := uint32(f)
の v の値は実装依存で、go 言語の仕様からはわからない、ということらしい。
とはいえこれは未定義動作ではなく「値は実装依存」なので、下記のコードは正しく動作すると思う。
ちょっと自信がないけれど。
C/C++ と同様、キャストすればよい。
こんな感じ。
func isInt32(x float64) bool {
	return float64(int32(x)) == x
}
結果
結果は下表の通り:
| 入力 | floor | modf | int32 | 
|---|---|---|---|
| 0.0 | true | true | true | 
| 1.0 | true | true | true | 
| 1.5 | false | false | false | 
| 1e18 | true | true | false | 
| 2e19 | true | true | false | 
| NAN | false | false | false | 
| INF | true | false | false | 
| -0.0 | true | true | true | 
| 2147483648 | true | true | false | 
| 2147483647 | true | true | true | 
| -2147483649 | true | true | false | 
| -2147483648 | true | true | true | 
というわけで
というわけで、符号小数点数が整数かどうかを調べる方法はたくさんある。
状況に応じて最適なものを選ぶのがよいけど、無駄な計算をしないように注意したい。