浮動小数点数が整数であるかどうかを調べたいことがあった。
で。
いくつか方法があるので紹介する。
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 |
というわけで
というわけで、符号小数点数が整数かどうかを調べる方法はたくさんある。
状況に応じて最適なものを選ぶのがよいけど、無駄な計算をしないように注意したい。