Add integral rounding functions to FloatingPoint
01 何が問題だったのか
浮動小数点数を整数値に丸める操作は非常に基本的なものですが、Swift の標準ライブラリにはこれに対応する関数が用意されていませんでした。他言語の標準ライブラリで一般的な floor() や ceil() に相当する機能を使うには、プラットフォームごとに Darwin(Apple 系)や Glibc(Linux)を import して C 標準ライブラリのグローバル関数を呼び出す必要があり、プラットフォームに依存しない素の Swift だけでは整数への予測可能な変換ができませんでした。
import Darwin // もしくは Glibc
let x = 4.5
let rounded = floor(x) // グローバル関数としての C API に頼る必要があります。
また、floor / ceil のような2種類の丸めだけでは不十分で、「最も近い整数に丸める(同距離の場合は偶数側)」「ゼロに近い側に丸める」など、用途に応じた複数の丸め方を統一的に扱う API もありませんでした。
02 どのように解決されるのか
FloatingPoint プロトコルに丸め操作を追加し、丸めルールを表す FloatingPointRoundingRule 列挙型と、それを受け取る rounded(_:) メソッド(および破壊的版の round(_:))を標準ライブラリで提供します。これにより、プラットフォーム依存のグローバル関数を import せずに、Swift 単体で整数丸めができるようになります。
丸めルールを表す FloatingPointRoundingRule
丸め方を表す列挙型が導入されます。IEEE 754 に準拠した丸め方に加え、実装中の議論で要望の多かった .awayFromZero(絶対値を大きくする方向)が含まれます。
public enum FloatingPointRoundingRule {
// 最も近い値に丸める。同距離なら絶対値の大きい方(いわゆる四捨五入)。
case toNearestOrAwayFromZero
// 最も近い値に丸める。同距離なら偶数側(いわゆる銀行家の丸め)。
case toNearestOrEven
// 元の値以上で最も近い値(切り上げ。C の ceil 相当)。
case up
// 元の値以下で最も近い値(切り捨て。C の floor 相当)。
case down
// 絶対値が元の値以下となる、最も近い値(ゼロ方向への切り詰め)。
case towardZero
// 絶対値が元の値以上となる、最も近い値。
case awayFromZero
}
FloatingPoint のメソッド
FloatingPoint 本体には、丸めルールを受け取る2つのメソッドが要求として追加されます。
protocol FloatingPoint {
// 指定したルールで丸めた値を返します。
func rounded(_ rule: FloatingPointRoundingRule) -> Self
// 指定したルールで自身を丸めます。
mutating func round(_ rule: FloatingPointRoundingRule)
}
さらに、引数を省略した場合に「四捨五入(.toNearestOrAwayFromZero)」となるデフォルト版が、プロトコル拡張として提供されます。
extension FloatingPoint {
public func rounded() -> Self {
return rounded(.toNearestOrAwayFromZero)
}
public mutating func round() {
round(.toNearestOrAwayFromZero)
}
}
使い方
インスタンスメソッドとして呼び出すため、他の数値操作と同じスタイルで丸め処理が書けます。
(4.4).rounded() // 4.0
(4.5).rounded() // 5.0 (同距離は絶対値が大きい方)
(-4.5).rounded() // -5.0
(4.0).rounded(.up) // 4.0 (既に整数ならそのまま)
(4.1).rounded(.up) // 5.0 (C の ceil 相当)
(4.9).rounded(.down) // 4.0 (C の floor 相当)
(4.5).rounded(.toNearestOrEven) // 4.0 (同距離は偶数側)
(5.5).rounded(.toNearestOrEven) // 6.0
(-1.5).rounded(.towardZero) // -1.0
(-1.5).rounded(.awayFromZero) // -2.0
破壊的に丸めたい場合は round(_:) を使います。
var x = 4.7
x.round(.down) // x は 4.0
影響範囲
追加的な変更であり、Darwin / Glibc の floor / ceil などを使う既存コードはそのまま動作します。ただし rounded(.up) / rounded(.down) がそれぞれ ceil / floor と等価であるため、新しいコードでは標準ライブラリ側のメソッドを使うのが自然です。