11
9

More than 3 years have passed since last update.

浮動小数点数の値が整数であるかどうかを調べる(C++, Ruby, JS, Go)

Last updated at Posted at 2019-02-23

浮動小数点数が整数であるかどうかを調べたいことがあった。

で。

いくつか方法があるので紹介する。

ruby

to_i を使う

こんな感じ。

ruby
def integer?(x)
  x==x.to_i;
end

なお。Integer に変換できない値を入力すると例外になる。

こんな感じ。

ruby
(Float::INFINITY).to_i #=> FloatDomainError: Infinity
(Float::NAN).to_i #=> FloatDomainError: NaN

それと。to_i メソッドは RationalInteger にも生えているのでそういう型の値を上記の関数に食べさせてもちゃんと動く。

あと。
ここでは to_i を使ったけど、ceil, round, truncate でも同じ結果になる。
ちなみに。
C++の std::floor(double)double を返すけど、ruby の Float#floor は、Integer を返す。要注意。

実行速度も測ってみたけど、大差ない感じ。ソースコードを見ると to_i が速そうではある。

ちなみに 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? があるんだから、大小比較すればよいというアイディア。
こんな感じ。

ruby
def is_int32_1(x)
  integer?(x) && (-2**31)<=x && x<=(2**31-1)
end

なんか 2**31 とか書くのがちょっと気分悪い。そういう方のために次の節。

pack / unpack を使う

ruby
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 を使う

こんな感じ。

c++
#include <cmath>
bool is_integer( double x ){
  return std::floor(x)==x;
}

std::floor が返すのは double ( 入力が float なら floatlong double なら long double) なので、is_integer(1e100)true になることに注意。

それと。
上記の例では std::floor を使ったけど、std::ceil, std::round, std::trunc, std::rint, std:: nearbyint でも同様。

しかしやたらたくさんある。

std::modf を使う

std::modf を使うという手もある。

こんな感じ。

c++
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 に入る範囲内の整数かどうかを調べることができる。
こんな感じ。

c++
#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 整数にキャストができるので、実装は簡単。

c++
#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 を使う

こんな感じ。

JavaScript
function is_integer(x) {
  return x == Math.floor(x);
}

大きな数や 無限 を入れると true になる。

こちらも Math.ceil(), Math.round(), Math.trunc() でも同じ。

bit or 演算子を使う

bit or 演算子を使うと急に 32bit 符号あり整数に変換されるという不思議な機能があるので、これを使えばいい。

こんな感じ。

JavaScript
function is_int_bitor(x) {
  return x == (x|0);
}

JavaScript に慣れてないので、ソースコード的には割と気持ち悪い感じがする。慣れればそうでもないんだと思う。

例えば 32bit 符号なし整数になるかどうかが知りたい場合

ruby と C++ では符号ありだったけど、JavaScript の 32bit 符号ありはもう bit or 演算子でやってしまったので、今回は符号なし。

TypedArray を使う

実装はこんな感じ。

JavaScript
function is_uint32_typedarray(x) {
  return (new Uint32Array([x]))[0]==x;
}

今どき Uint32Array が使えない環境も少ないと思うけど、そういう不幸な方は次の節を。

比較する

ruby で行ったように、整数かどうか判断した上で値が uint32 の範囲に入っているかどうか調べるという手もある。

こんな感じ。

JavaScript
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 を使う

普通に。こんな感じ。

go
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 がある。

こんな感じ。

go
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++ と同様、キャストすればよい。

こんな感じ。

go
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

というわけで

というわけで、符号小数点数が整数かどうかを調べる方法はたくさんある。
状況に応じて最適なものを選ぶのがよいけど、無駄な計算をしないように注意したい。

11
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9