https://youtu.be/80_rPL-uplQ
今回はThe Basics
の 整数と浮動小数点数の変換
についての続きから。前回は主に 整数型
から 浮動小数点数型
への変換を見ていきましたけれど、今回はその逆の 浮動小数点数型
から 整数型
への変換に着目していく回になりそうです。前回に話したことと重なるところもありそうですけれど、せっかくなのでもう少し細かく 型変換
について見渡してみることにしますね。どうぞよろしくお願いします。
————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #124
00:00 開始 01:23 スティッキービットの役割の話の間違い訂正 01:49 スティッキービットは馴染みあるもの? 02:05 スティッキービットの役割 02:39 /tmp とスティッキービット 04:01 スティッキービットの動きを確かめてみる(失敗) 05:53 Open Directory とアカウント 06:26 ディレクトリーのパーミッション調整 09:27 setuid と setgid を使ってみる(失敗) 11:53 setuid や setgid が使われる場面 12:15 CGI と setuid, setgid 12:51 mac の ping には setuid がセットされていない 14:06 前回の「8真数のビットシフト」についての訂正 15:00 浮動小数点数から整数への型変換 15:38 イニシャライザーを通して変換 16:26 負の値の丸め方向 17:44 切り捨てることで大きくなる数 18:58 インスタンスの変換とリテラルの変換の違い 22:35 さまざまな丸め込み方 23:08 負の無限大方向への丸め 24:02 ParseStrategy 25:23 浮動小数点数に対する丸めの方法 26:07 既定に丸め方法 27:05 丸めを型変換と捉えるかどうか 29:52 丸めがメソッドで搭載されている理由 31:01 通常の丸めと、型変換の丸め 32:48 丸めを伴う型変換の実装を汎用化してみる —————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #124
はい、じゃあ始めていきましょう。今日は、不動小数点数から整数への変換について中心に話を進めていきます。前回もお話ししましたが、今回は復習も兼ねて、ゆっくり見ていきたいと思います。それでは進めていきましょう。
スライドに入る前に少し補足がありますが、これは大した補足ではありません。5〜6回前の動画を見返してみたときに、いくつか説明不足や誤解を招きそうなところがあったので、その補足と訂正を行いたいと思います。
まず、スティッキービットについてですが、以前の勉強会で「スティッキービットがルート権限で実行できる」といった誤解を招くような説明をしてしまいました。実際には、スティッキービットはディレクトリにセットされている場合、そのディレクトリ内のファイルは自分が作成したファイルしか削除できないようにするパーミッション設定です。この設定により、他のユーザーが誤ってファイルを削除してしまうことを防ぐことができます。
スティッキービットについてはLinuxやMacOSで利用されていますが、昔のシステムでよく見られたものなので、今のシステムではどうなっているかはまた検証する必要がありますね。
次に、具体的な例として、ディレクトリにスティッキービットを設定する方法をお話しします。例えば、/tmp
ディレクトリなどにはスティッキービットが設定されています。この設定があるディレクトリでは、自分が作ったファイルしか削除できないので、管理が簡単になります。
他にも、実際の操作について少し補足しておくと、たとえば、あるディレクトリの書き込み権限や削除権限を確認する場合には、以下のようなコマンドを使います。
$ ls -ld /path/to/directory
このコマンドを使うと、該当ディレクトリの詳細な権限情報が表示されます。また、スティッキービットを設定するには以下のようなコマンドを使います。
$ chmod +t /path/to/directory
スティッキービットを取り消す場合には、以下のコマンドを使います。
$ chmod -t /path/to/directory
続いて、setuidやsetgidビットについても少し触れておきます。これらのビットは、実行権限を変えるための機能を持っています。setuidビットが設定されているプログラムは、そのプログラムがルートユーザーの権限で実行されるようになります。同様に、setgidビットが設定されている場合、そのプログラムは属するグループの権限で実行されます。
例として、以下のようなコマンドでこれらのビットを設定します。
$ chmod u+s /path/to/file # setuidビットを設定
$ chmod g+s /path/to/file # setgidビットを設定
このように、パーミッションや権限設定にはさまざまな方法と効果があり、それぞれのビットがどのように動作するかを理解しておくことが重要です。
それでは、Swiftの話に戻ります。整数と不動小数点数の変換について具体的に見ていきましょう。まず、Swiftでは非常に簡単に型変換ができます。不動小数点数から整数への変換の基本的な方法は以下の通りです。
let floatingPointNumber: Double = 10.75
let integerNumber: Int = Int(floatingPointNumber)
この例では、Double
型の値10.75
をInt
型に変換しています。この場合、小数部分は切り捨てられ整数値の10
が得られます。この基本的な変換方法を理解した上で、より複雑な変換やエラーハンドリングについても触れていきたいと思います。 例えば、シェルスクリプトを作ったときに super
コマンドを使うと、実行中のユーザーが表示されるという使い方があります。これを実行権限を付けて動かすと、自分の名前が表示されます。そして、オーナーを root
に設定した場合には、動かすと表示が変わるわけです。
ここで、chmod
コマンドを使ってユーザーに対して setuid
権限を付けるとどうなるかというと、現在のユーザーアカウントで実行したときに違いが生じることがあります。ディレクトリの権限が設定されている場合など、特定の動作に影響を与えることがありますが、詳細な挙動は Linux と macOS で異なる場合があります。
setuid
や setgid
は、ユーザーやグループの権限を設定するためのフラグです。例えば、ユーザーに対して setuid
を設定すると、そのユーザーの権限でスクリプトやプログラムが実行されます。そして、グループに対して setgid
を設定すると、そのグループの権限で実行されるようになります。
過去には、特定の権限が必要なシェルスクリプトやバイナリを実行するためにこれらのフラグが活用されていました。たとえば、一部のコマンドは特定の権限がないと実行できないことがありますが、setuid
を設定することで一般ユーザーでも実行可能にする場合がありました。
ping
コマンドについては、ネットワーク周りの操作に通常は root
権限が必要ですが、一般ユーザーでもネットワークの状況を調査できるようにするために setuid
を設定することがあります。これによって、ルート権限を持たないユーザーでも ping
コマンドを実行することができるようになります。
話題を変えて、前回の勉強会での内容について補足します。前回は4ビットだから4回シフトという話をしましたが、実際には3ビットが正解です。具体的には、8進数(0〜7)は3ビットで表現されます。
0は 000
、1は 001
、2は 010
、3は 011
、4は 100
、5は 101
、6は 110
、7は 111
で表現されます。これを2桁目に移すためには3ビットシフトが必要です。誤って4ビットシフトと言っていましたが、3ビットが正解です。
さて、本題に入ります。前回は整数から浮動小数点数への変換についてお話しましたが、今回はその逆、つまり浮動小数点数から整数型への変換も取り上げます。 「整数型はダブル型やフロー型の値から初期化可能」という記述がありますが、要は「変換可能」ということです。初期化可能と書いてある理由は、以前にお話ししたように、型変換はイニシャライザーを通じて行われるためです。イニシャライザーを通すことで変換先の型へ変換するため、「初期化可能」という表現を使っています。
この例では円周率を示す変数 pi
を使って整数型 Int
へ変換する場面を考えています。pi
を使って Int
型を初期化可能としており、その際には明示的に変換を行っています。
ここで興味深いのは、負の値の変換です。これは0に近づく方に丸め込まれるということです。大して意識しなくても理解しやすい部分ですが、実際に0に寄せていく動作はポイントとして押さえておきたいところです。
具体例をあげると、ダブル型 a
として 10.5
があるとします。そして、b
を Int
型に変換した場合、a
が 10.5
で b
が 10
となります。a
が b
より小さいため、型を合わせる必要がでてきます。この場合、Double
型に合わせると、一度 Int
に丸め込まれてから Double
に戻されると元の値より小さくなるわけです。
次に、a
が負の値の場合はどうなるかを見ると、例えば -10.5
を Int
に丸め込むと -10
になります。ただし、-0.5
という部分は切り捨てられ、0に向かって丸められるため、b
の値は -10
となるわけです。-10.5
より大きくなるので、Int
に変換されたときの挙動として対称関係が小さくなるわけではなく、あくまで0方向に近づいているということを理解することが重要です。
この部分は特にプログラムを書く際に理解しておくべき点であり、誤解が生じやすいので注意が必要です。 こういう風に丸め込みが起こることを気にしておくとよいでしょう。そして次に、定数への変換についてもう少し見ていきたいと思いますが、まずは別のスライドを確認しましょう。
不動小数点数と整数型の変換について話します。この部分では、変数や定数の値を組み合わせる際のルールと数値リテラルの際のルールが異なります。この説明はスライドを書いたときに理解していましたが、いきなり聞かされると分かりにくいかもしれません。
ここでは、数値リテラルは明確な型を持っていないため、リテラルの3
はリテラルの0.14159
に直接変換できますが、リテラルではない同士だと変換する必要があります。具体的には、数値リテラルの型はコンパイラーで処理されるときに初めて推定されるので、ルールが異なります。
例えば次のようなコードを考えてみましょう。
var a = 1 + 0.5 // この行は大丈夫
しかし、次のようなコードはコンパイルエラーになります。
var b = 1
var c = 0.5
var d = b + c // この行がエラーになる
なぜなら、b
は整数(Int
)で定義され、c
は浮動小数点数(Double
)で定義されているからです。計算する際に、これらの型を一致させる必要があります。それに対して、最初の行は数値リテラルそのものには型が存在しないため、コンパイルタイムに推論されて型が決まります。 なので、この演算を行うときに両方が同じ型でなければならない場合、デフォルトでは整数リテラルは Int
型、小数点リテラルは Double
型です。しかし、整数リテラルは Double
型としても振る舞える互換性があるため、両方を Double
型に変換します。コンパイルタイムにこれがキャスト(型変換)されるということですね。これは型変換のルールというよりは、リテラルの扱いのルールです。
仮にこのリテラルが Int
型として解釈される場合は、どちらも同じ型として処理が可能でなければなりません。この意味で、リテラルも広く捉えると型変換になります。「リテラルも型変換の一部として扱っても良いのではないか」などと言及していたスライドについての説明です。混乱を避けるためには、このスライドは認識しない方がスムーズにコードを書ける場合もあるかもしれません。
このように型変換の話が終了です。しかし、その他にも変換イニシャライザーが用意されているので、残りの時間でそれを見ていきましょう。たとえば、負の Double
型の値を Int
型にキャストする場合、その際には0に向けて丸め込まれるという話をしました。しかし、場合によっては値が小さくなる方向に丸め込みたいこともあります。たとえば、-10.5
を -11
に丸め込みたい場合などです。
通常、値を小さく丸め込む方が都合が良いということも考えられます。そのようなときには、Int
型のイニシャライザー(例えば、Double
をトランケートして Int
型にすることなど)が用意されているかもしれません。Double
型のコードだと、パースメソッドが存在します。例えば、パースインプットトラテジーなどの用語が出てきますが、詳細についてはコードやドキュメントを参照してください。一回見てみると良いでしょう。
丸めに関して言えば、Int
型には特定のトランケーティングメソッドはなさそうですが、その他のトラテジーやイニシャライザーなどで対応できるかもしれません。これで型変換に関する基本的な説明を終わります。 とりあえず、このトランケーションは切り捨てですね。だから普通に A
に対して rounded
を使う感じです。rounded
にはいろいろなオプションがあります。例えば、下方向に丸める場合は round(.down)
といった感じになります。他にもいくつかのモードがありますが、まぁこれも基本的には rounded
です。
さて、本題に戻ります。rounded
には切り上げや切り捨てのオプションがあるので、用途に応じて使い分けるとよいでしょう。デフォルトの動作では、init
メソッドを使って Double
型を取り扱うことになりますね。特定の Double
の操作をしながら、型変換が同時に行われることもあります。型変換はプログラムを書く上でよく使う機能です。
rounded
について話したいのですが、まず、型変換の話からいきましょう。型変換を行うときは Int
型への変換を考えるのが一般的です。デフォルトでは単純な四捨五入が行われますが、例えば FloatingPointRoundingRule
で特定のルールを指定することができます。
例えば、以下のように書くことで四捨五入の規則を指定できます。
let roundedValue = value.rounded(.towardZero)
こうすることで、value
を 0 に向かって丸めることができます。このように、型変換を行うメソッドは作りながら直感的にわかるように実装することが大切です。
また、Int
型のイニシャライザを使うとか、変換のルールを明示的に定義するといったこともあります。ただ、実際には Double
型の丸めを行うメソッドである rounded
を使っているので、それに忠実に実装したほうがわかりやすいです。
例えば、次のように書くことで Double
型の数値を指定したルールで丸めることができます。
let value: Double = -11.0
let roundedValue = value.rounded(.toNearestOrAwayFromZero)
このように書くことで、value
が四捨五入されます。具体的な使用例としては、切り捨てや切り上げなど、要求に応じた丸めを行うことができます。
最後に、このようなメソッドを使う場合には、APIデザインガイドラインに従って、なるべく直感的でわかりやすいコードを書くことを心がけましょう。これは、他の開発者がコードを読んだりメンテナンスしたりするときに非常に重要です。
ですので、rounded
メソッドは、単に型変換を行うだけでなく、四捨五入のルールを適用するためのメソッドとしても使うことが可能です。この考え方をもとに、プログラムを書くとよいでしょう。 これは、わざわざ型変換でもないのに、バブル型に対してAを渡して、これでルール化しています。こうやって書いていく方がとても分かりにくいですね。型変換にとらわれましたが、そういう違いです。純粋に自分自身の演算というか、自分自身の一つの型変換を行って提供できるという感じです。
さて、いい具合に話したいことも大体話し終えられた気がします。変換イニシャライザについてですが、独自の解釈とビットが担当して解釈する場合、ラベルが普通です。ラベルがなくてもパラメータが複数あるときがあり、これはフォーマットと言って取る形です。例えば、Int
のテキストフォーマットでデニエットごとに読みます。
やっぱりラベルは簡単と考えますね。フォーマッティングの話か何か分かりにくい場合は、ルールで決めるのがいいかもしれません。確かに、APIデザインガイドラインの解釈も微妙なところがあり、少なくとも自分の意識ではラウンディッドよりもルールを優先しました。イニシャライザで変換していきますが、これで試してみましょうか。時間もそろそろ終わりますし、変換のルールを置いておいて、もう少し汎用的にできるか見てみます。
例えば、フローティングポイントで試してみます。FloatingPoint
がRounded
を持っているかどうか確認します。以下のように書きます。
let value = value.rounded(.toNearestOrEven)
これでルールが適用されますね。そして次に、Int
型に対して試します。ここでエラーが出ていました。value
が何型かというと、FloatingPoint
型ですね。この型からInt
型に変換できるか試します。以下のようにします。
self = Int(value)
フローティングポイント型からInt
型に変換するイニシャライザはちょっとないようですね。これができないと汎用的な変換は難しいですが、Double
とFloat
でオーバーロードすればできるかもしれません。
ちょっと試してみます。まずDouble
型で自己代入します。
self = self.rounded(.toNearestOrEven)
同じノリでFloat
型でも作れば、浮動小数点数型からInt
型への汎用的なものができます。さらにBinaryFloatingPoint
を使うことで、もっと汎用的な変換が可能かもしれません。これは勉強会が終わった後に、自分で試してみます。
何にしても、時間になりましたので、今日はこれで勉強会を終わりにします。