【Swift】小数の丸め処理(round, rounded)のまとめ

DoubleやFloat、CGFloatなどを丸める(切り上げ、切り捨て、四捨五入など)ための関数として、Swiftの標準ライブラリに roundrounded が追加されて久しいのはみなさんご存知のことかと思います。

ただ、これに渡すルール(FloatingPointRoundingRuleの値)をいつも迷うため、この記事にてまとめたいと思います。

ここではSwift5.3で記載していますが、round, rounded自体は3.0から追加されています。

最初に結論から

こんな感じになります。

元の値6.55.55.4-5.4-5.5-6.5
.up7.06.06.0-5.0-5.0-6.0
.down6.05.05.0-6.0-6.0-7.0
.towardZero6.05.05.0-5.0-5.0-6.0
.awayFromZero7.06.06.0-6.0-6.0-7.0
.toNearestOrAwayFromZero7.06.05.0-5.0-6.0-7.0
.toNearestOrEven6.06.05.0-5.0-6.0-6.0

以下、round, rounded の詳細について記載していきます。

round, roundedの定義

round と rounded の定義は以下のようになっています。(Doubleの場合)

public func rounded() -> Double
public func rounded(_ rule: FloatingPointRoundingRule) -> Double

public mutating func round()
public mutating func round(_ rule: FloatingPointRoundingRule)

使い方としては以下のようになります。

let roundedValue = (5.5).rounded(.up)
print(roundedValue) // 6.0

var value = 5.5
value.round(.down)
print(value) // 5.0

roundedは丸めた値を返して、元の値は変更しません。
上では数値リテラルから呼び出していますが、もちろん定数や変数からでも問題ありません。

roundはmutatingとなっており、変数から呼び出して自らの値を変更します。
こちらは変数からのみ呼び出し可能となっています。

FloatingPointRoundingRule

ruleの引数に渡す FloatingPointRoundingRule の値とその意味は以下になります。

.up

切り上げを行います。

切り上げとは、端数が生じた場合、数の大きい方へ寄せて丸める処理のことを言います。
5.x であれば 6.0 に、-5.x であれば -5.0 になります。(xは0以外の任意の数字)

ceil関数もこれと同じ結果になります。

.down

切り捨てを行います。

切り捨てとは、端数が生じた場合、数の小さい方へ寄せて丸める処理のことを言います。
5.x であれば 5.0 に、-5.x であれば -6.0 になります。(xは0以外の任意の数字)

floor関数もこれと同じ結果になります。

.towardZero

0に近くなるように丸めます。

これは、端数が生じた場合、0に近い数へ寄せて丸める処理を行います。
5.x であれば 5.0 に、-5.x であれば -5.0 になります。(xは0以外の任意の数字)

.awayFromZero

0から遠くなるように丸めます。

これは、端数が生じた場合、0から遠い数へ寄せて丸める処理を行います。
5.x であれば 6.0 に、-5.x であれば -6.0 になります。(xは0以外の任意の数字)

.toNearestOrAwayFromZero

いわゆる四捨五入をします。

これは、まずtoNearestOrAwayFromZeroの名前のとおり、x.5 (xは正負関わらず任意の数字)を境として、より近い方の整数に寄せます。5.0 < n < 5.5 なら 5.0 に、5.5 < n < 6.0 なら 6.0 になります。負数も同様に、-5.5 < n < -5.0 なら -5.0 に、-6.0 < n < -5.5 なら -6.0 になります。

そして、ちょうど x.5 だった場合は、toNearestOrAwayFromZeroのとおり0から遠い方へ寄せます。5.5 なら 6.0 に、-5.5 なら -6.0 になります。

ちなみに、Appleのリファレンスを見ると以下のような記述があります。

This rounding rule is also known as “schoolbook rounding.”

Apple Developer Document – https://developer.apple.com/documentation/swift/floatingpointroundingrule/tonearestorawayfromzero

「教科書の丸め処理」ってことでしょうか。
アメリカも日本と同様、学校で習うアレという認識なんですね😊

.toNearestOrEven

銀行家の丸め (bankers rounding) と呼ばれる丸め処理を行います。

まずNearestの部分は上記 toNearestOrAwayFromZero と同じです。近い方に寄せます。

そしてちょうど x.5 の場合、Even の名が示すとおり、偶数になるように丸めます。
5.5 なら 6.0 に、6.5 なら 7.0 にはならず 6.0 になります。負数も同様、-5.5-6.0 に、-6.5-6.0 になります。

この「銀行家の丸め」については、以下のサイト様の記事がとても参考になりました 🙇‍♂️ 感謝
会計SEのメモ – 「銀行家の丸め」とは何か

要するに、数値を丸めながら集計を行った場合、単純な四捨五入よりも誤差が少なくなるので銀行家に好まれた、ということのようです。
おもしろい。

お試し用コード

最後に、自分で動かして確認したい方向けにサンプルコードを載せておきます。
PlaygroundやXCTestなどにコピペしてお使いください。

import Foundation

let values: [Double] = [6.51, 6.5, 6.49, 5.51, 5.5, 5.49, 5.4, -5.4, -5.49, -5.5, -5.51, -6.49, -6.5, -6.51]
let rules: [FloatingPointRoundingRule] = [.up, .down, .towardZero, .awayFromZero, .toNearestOrAwayFromZero, .toNearestOrEven]

// rounded
rules.forEach { rule in
    print("rule: \(rule)")
    values.forEach { value in
        print("\(value) -> \(value.rounded(rule))")
    }
    print("")
}

// round
print("round")
values.forEach { value in
    print("\(value) -> \(round(value))")
}
print("")

// ceil
print("ceil")
values.forEach { value in
    print("\(value) -> \(ceil(value))")
}
print("")

// floor
print("floor")
values.forEach { value in
    print("\(value) -> \(floor(value))")
}
print("")

参考リンク

Apple Developer Document – FloatingPointRoundingRule
https://developer.apple.com/documentation/swift/floatingpointroundingrule