各言語での四捨五入 round メソッドを見る

# 自己紹介

[ハトネコエ](https://twitter.com/nekonenene)です。

「プログラマのための数学LT会」を主催しております。

http://nekonenene.hatenablog.com/entry/math4pg-lt2

次回の第３回を年明けに開催したい気持ちですので、その際はみなさまご登壇などのご協力をよろしくお願いします。


# 前置き

四捨五入と言えば、 1.4 が 1 になり、 1.5 が 2 になることでおなじみです。

ですが、ここで <b>2.5 を四捨五入すると 2 になる</b> と聞くと驚くでしょうか？
私は驚きました、知りませんでした。

JIS Z 8401 や ISO 31-0 （およびその改定である ISO 80000-1 ）や IEEE 754 といった標準規格の制定書には、
1.5 や 2.5 のような、丸め先の整数に同じだけ近い数（小数点第１位が５である小数）は、偶数となるほうに丸めるという方法が紹介されています。
そのほうが誤差が少なくなるそうです。

こちらの記事が詳しいです。（※記事中に ISO 3110 とありますが、それは ISO 31-0 の誤りです）
[JIS, ISO 式四捨五入](http://www.okadajp.org/RWiki/?JIS%2CISO%E5%BC%8F%E5%9B%9B%E6%8D%A8%E4%BA%94%E5%85%A5)

では、各プログラミング言語やソフトウェアでは四捨五入はどのような実装になっているのでしょうか？
気になって、いくつかのプログラミング言語での `round` メソッドの挙動を見てみることにしました。


# 各言語で四捨五入してみよう

* 1.5 + 2.5 + 3.5 + 4.5 = 12
* 1.5 * 2.5 * 3.5 = 13.125
* -0.5 + -1.5 + -2.5 = -4.5

これらの式について、それぞれの数を四捨五入した上で計算してみましょう。

各プログラミング言語での実行にあたっては **[paiza.io](https://paiza.io/ja)** を大いに活用させていただきました。ありがとうございました。

以下、2.5を四捨五入して3にするものは学校教育と同じなので _School_ タイプ、近い偶数である2にするものは _Even_ タイプと記すことにします。
（これは[Swiftのドキュメント](https://developer.apple.com/documentation/swift/double/2886735-rounded)にて "schoolbook rounding" という表記がなされていることに影響されています）


## C++

_School!_

```cpp
#include <iostream>
#include <cmath>
int main(void){
    std::cout << std::round(1.5) + std::round(2.5) + std::round(3.5) + std::round(4.5) << '\n'; // 14 = 2 + 3 + 4 + 5
    std::cout << std::round(1.5) * std::round(2.5) * std::round(3.5) << '\n'; // 24 = 2 * 3 * 4
    std::cout << std::round(-0.5) + std::round(-1.5) + std::round(-2.5) << '\n'; // -6 = -1 + -2 + -3
}
```


## C#  

_Even!_

```csharp
using System;

public class Test{
    public static void Main(){
        Console.WriteLine(Math.Round(1.5) + Math.Round(2.5) + Math.Round(3.5) + Math.Round(4.5)); // 12 = 2 + 2 + 4 + 4
        Console.WriteLine(Math.Round(1.5) * Math.Round(2.5) * Math.Round(3.5)); // 16 = 2 * 2 * 4
        Console.WriteLine(Math.Round(-0.5) + Math.Round(-1.5) + Math.Round(-2.5)); // -4 = 0 + -2 + -2

        Console.WriteLine(Math.Round(1.5, MidpointRounding.AwayFromZero) + Math.Round(2.5, MidpointRounding.AwayFromZero) + Math.Round(3.5, MidpointRounding.AwayFromZero) + Math.Round(4.5, MidpointRounding.AwayFromZero)); // 14 = 2 + 3 + 4 + 5
        Console.WriteLine(Math.Round(1.5, MidpointRounding.AwayFromZero) * Math.Round(2.5, MidpointRounding.AwayFromZero) * Math.Round(3.5, MidpointRounding.AwayFromZero)); // 24 = 2 * 3 * 4
        Console.WriteLine(Math.Round(-0.5, MidpointRounding.AwayFromZero) + Math.Round(-1.5, MidpointRounding.AwayFromZero) + Math.Round(-2.5, MidpointRounding.AwayFromZero)); // -6 = -1 + -2 + -3
    }
}
```

C#での `Round` はデフォルトではEven型ですが、
引数に `MidpointRounding.AwayFromZero` を指定してあげるとSchool型になります。

デフォルトの場合は、引数に `MidpointRounding.ToEven` が指定されているときと同じ挙動のようです。


## Java

_School!_

```java
System.out.println(Math.round(1.5) + Math.round(2.5) + Math.round(3.5) + Math.round(4.5)); // 14 = 2 + 3 + 4 + 5
System.out.println(Math.round(1.5) * Math.round(2.5) * Math.round(3.5)); // 24 = 2 * 3 * 4
System.out.println(Math.round(-0.5) + Math.round(-1.5) + Math.round(-2.5)); // -3 = 0 + -1 + -2
```

注目すべきは負の数での四捨五入です。

今回検証した中では Java と JavaScript だけがこの挙動を見せました。
-0.4 は 0 に、 -0.6 は -1 に round されますが、 -0.5 は 0 に round されます。
小数点第１位が５であるときは必ず ＋∞ 方向へ繰り上げがなされるようですね。
他の言語では絶対値としての四捨五入でしたので、この違いは注意です。


## Kotlin

_Even!_

`round` メソッドは Kotlin 1.2 から導入されています。
( http://kotlinlang.org/api/latest/jvm/stdlib/kotlin.math/round.html )

paiza.io はまだ1.1系ですので [Playground](https://try.kotlinlang.org/) を使うか、もしくはコンソールから `kotlinc-jvm` でREPLを呼び出すことで試せます。

```kotlin
println(kotlin.math.round(1.5) + kotlin.math.round(2.5) + kotlin.math.round(3.5) + kotlin.math.round(4.5)) // 12.0 = 2 + 2 + 4 + 4
println(kotlin.math.round(1.5) * kotlin.math.round(2.5) * kotlin.math.round(3.5)) // 16.0 = 2 * 2 * 4
println(kotlin.math.round(-0.5) + kotlin.math.round(-1.5) + kotlin.math.round(-2.5)) // -4.0 = 0 + -2 + -2
```

これは内部実装としてはJavaの `Math.rint` を呼んでいるだけなので、以下のようにしても同じ結果になります。

```kotlin
println(Math.rint(1.5) + Math.rint(2.5) + Math.rint(3.5) + Math.rint(4.5)) // 12.0 = 2 + 2 + 4 + 4
println(Math.rint(1.5) * Math.rint(2.5) * Math.rint(3.5)) // 16.0 = 2 * 2 * 4
println(Math.rint(-0.5) + Math.rint(-1.5) + Math.rint(-2.5)) // -4.0 = 0 + -2 + -2
```

もちろん `Math.round` を利用すると、先ほどのJavaと同じ結果になります。

```kotlin
println(Math.round(1.5) + Math.round(2.5) + Math.round(3.5) + Math.round(4.5)); // 14 = 2 + 3 + 4 + 5
println(Math.round(1.5) * Math.round(2.5) * Math.round(3.5)); // 24 = 2 * 3 * 4
println(Math.round(-0.5) + Math.round(-1.5) + Math.round(-2.5)) // -3 = 0 + -1 + -2
```


## JavaScript

_School!_

Javaと名前が似ているJavaScriptですがコードも似ています。
そしてJavaと同じ結果を示します。

```js
console.log(Math.round(1.5) + Math.round(2.5) + Math.round(3.5) + Math.round(4.5)); // 14 = 2 + 3 + 4 + 5
console.log(Math.round(1.5) * Math.round(2.5) * Math.round(3.5)); // 24 = 2 * 3 * 4
console.log(Math.round(-0.5) + Math.round(-1.5) + Math.round(-2.5)); // -3 = 0 + -1 + -2
```


## R言語

_Even!_

数値計算でよく使われるプログラミング言語なだけあって、やはり誤差が少なくなる偶数への丸めの手法をとっています。

```r
cat(round(1.5) + round(2.5) + round(3.5) + round(4.5)) # 12 = 2 + 2 + 4 + 4
cat(round(1.5) * round(2.5) * round(3.5)) # 16 = 2 * 2 * 4
cat(round(-0.5) + round(-1.5) + round(-2.5)) # -4 = 0 + -2 + -2
```


## Python2

_School!_

```py
print(round(1.5) + round(2.5) + round(3.5) + round(4.5)) # 14.0 = 2 + 3 + 4 + 5
print(round(1.5) * round(2.5) * round(3.5)) # 24.0 = 2 * 3 * 4
print(round(-0.5) + round(-1.5) + round(-2.5)) # -6.0 = -1 + -2 + -3
```

## Python3

_Even!_

```py3
print(round(1.5) + round(2.5) + round(3.5) + round(4.5)) # 12 = 2 + 2 + 4 + 4
print(round(1.5) * round(2.5) * round(3.5)) # 16 = 2 * 2 * 4
print(round(-0.5) + round(-1.5) + round(-2.5)) # -4 = 0 + -2 + -2
```

おもしろいことに<b>Python2と違う結果</b>になりました。
Python2のコードをPython3へ移植するときは要注意なポイントです。


## NumPy

_Even!_

Pythonの数値計算ライブラリである **NumPy** を用いた `round` も試してみました。
結果は以下の通りで、これはありがたいことに<b>Python2でもPython3でも同じ実行結果</b>を示してくれます。

```py
import numpy

print(numpy.round(1.5) + numpy.round(2.5) + numpy.round(3.5) + numpy.round(4.5)) # 12.0 = 2 + 2 + 4 + 4
print(numpy.round(1.5) * numpy.round(2.5) * numpy.round(3.5)) # 16.0 = 2 * 2 * 4
print(numpy.round(-0.5) + numpy.round(-1.5) + numpy.round(-2.5)) # -4.0 = 0 + -2 + -2
```


## PHP

_School!_

```php
echo round(1.5) + round(2.5) + round(3.5) + round(4.5) . "\n"; // 14 = 2 + 3 + 4 + 5
echo round(1.5) * round(2.5) * round(3.5) . "\n"; // 24 = 2 * 3 * 4
echo round(-0.5) + round(-1.5) + round(-2.5) . "\n"; // -6 = -1 + -2 + -3
```


## Ruby

_School!_

```ruby
puts 1.5.round + 2.5.round + 3.5.round + 4.5.round # 14 = 2 + 3 + 4 + 5
puts 1.5.round * 2.5.round * 3.5.round # 24 = 2 * 3 * 4
puts -0.5.round + -1.5.round + -2.5.round # -6 = -1 + -2 + -3
```

デフォルトではSchoolの結果です。しかし、キーワード引数halfを指定することで、Evenの挙動にすることが可能です。

```ruby
puts 1.5.round(half: :even) + 2.5.round(half: :even) + 3.5.round(half: :even) + 4.5.round(half: :even) # 12 = 2 + 2 + 4 + 4
puts 1.5.round(half: :even) * 2.5.round(half: :even) * 3.5.round(half: :even) # 16 = 2 * 2 * 4
puts -0.5.round(half: :even) + -1.5.round(half: :even) + -2.5.round(half: :even) # -4 = 0 + -2 + -2
```

また、小数点第１位が５のときは切り上げをおこなう up、切り捨てをおこなう down も、引数として指定できます。
負の数に関しては、絶対値における切り上げ・切り捨てがおこなわれます。

```ruby
puts 1.5.round(half: :up) + 2.5.round(half: :up) + 3.5.round(half: :up) + 4.5.round(half: :up) # 14 = 2 + 3 + 4 + 5
puts 1.5.round(half: :up) * 2.5.round(half: :up) * 3.5.round(half: :up) # 24 = 2 * 3 * 4
puts -0.5.round(half: :up) + -1.5.round(half: :up) + -2.5.round(half: :up) # -6 = -1 + -2 + -3
```

```ruby
puts 1.5.round(half: :down) + 2.5.round(half: :down) + 3.5.round(half: :down) + 4.5.round(half: :down) # 10 = 1 + 2 + 3 + 4
puts 1.5.round(half: :down) * 2.5.round(half: :down) * 3.5.round(half: :down) # 6 = 1 * 2 * 3
puts -0.5.round(half: :down) + -1.5.round(half: :down) + -2.5.round(half: :down) # -3 = 0 + -1 + -2
```


## Swift

_School!_

```swift
var a = 1.5; var b = 2.5; var c = 3.5; var d = 4.5
a.round(); b.round(); c.round(); d.round()
print(a + b + c + d) // 14.0 = 2 + 3 + 4 + 5
print(a * b * c) // 24.0 = 2 * 3 * 4

a = -0.5; b = -1.5; c = -2.5
a.round(); b.round(); c.round()
print(a + b + c) // -6.0 = -1 + -2 + -3
```

`round()` の挙動は `rounded(.toNearestOrAwayFromZero)` の挙動と同じです。

```swift
print(1.5.rounded(.toNearestOrAwayFromZero) + 2.5.rounded(.toNearestOrAwayFromZero) + 3.5.rounded(.toNearestOrAwayFromZero) + 4.5.rounded(.toNearestOrAwayFromZero)) // 14.0 = 2 + 3 + 4 + 5
print(1.5.rounded(.toNearestOrAwayFromZero) * 2.5.rounded(.toNearestOrAwayFromZero) * 3.5.rounded(.toNearestOrAwayFromZero)) // 24.0 = 2 * 3 * 4
print(-0.5.rounded(.toNearestOrAwayFromZero) + -1.5.rounded(.toNearestOrAwayFromZero) + -2.5.rounded(.toNearestOrAwayFromZero)) // -6.0 = -1 + -2 + -3
```

一方で `rounded(.toNearestOrEven)` の挙動は異なります。
OrEvenという名前の付いている通り、小数点第１位が５であれば、近い偶数への丸めがおこなわれます。

```swift
print(1.5.rounded(.toNearestOrEven) + 2.5.rounded(.toNearestOrEven) + 3.5.rounded(.toNearestOrEven) + 4.5.rounded(.toNearestOrEven)) // 12.0 = 2 + 2 + 4 + 4
print(1.5.rounded(.toNearestOrEven) * 2.5.rounded(.toNearestOrEven) * 3.5.rounded(.toNearestOrEven)) // 16.0 = 2 * 2 * 4
print(-0.5.rounded(.toNearestOrEven) + -1.5.rounded(.toNearestOrEven) + -2.5.rounded(.toNearestOrEven)) // -4.0 = 0 + -2 + -2
```


## MySQL

_School!_

```mysql
SELECT ROUND(1.5) + ROUND(2.5) + ROUND(3.5) + ROUND(4.5); -- 14 = 2 + 3 + 4 + 5
SELECT ROUND(1.5) * ROUND(2.5) * ROUND(3.5); -- 24 = 2 * 3 * 4
SELECT ROUND(-0.5) + ROUND(-1.5) + ROUND(-2.5); -- -6 = -1 + -2 + -3
```


## Googleスプレッドシート (Google Sheets)

スプレッドシートの式にコメントはないのですが、MySQL同様 `--` をコメントだと思って以下お読みください。

```mysql
=ROUND(1.5) + ROUND(2.5) + ROUND(3.5) + ROUND(4.5) -- 14 = 2 + 3 + 4 + 5
=ROUND(1.5) * ROUND(2.5) * ROUND(3.5) -- 24 = 2 * 3 * 4
=ROUND(-0.5) + ROUND(-1.5) + ROUND(-2.5) -- -6 = -1 + -2 + -3
```

なお、 `ROUNDUP` , `ROUNDDOWN` という関数がありますが、こちらはそれぞれ「切り上げ」「切り捨て」に該当します。
Rubyの `round(half: :up)` や `round(half: :down)` とは違いますのでご注意を。

ちなみに

```mysql
=ROUNDUP(-0.5) + ROUNDUP(-1.5) + ROUNDUP(-2.5) -- -6 =  -1 + -2 + -3
=ROUNDDOWN(-0.5) + ROUNDDOWN(-1.5) + ROUNDDOWN(-2.5) -- -3 = 0 + -1 + -2
```

負の数における切り上げ・切り捨ては、絶対値での評価がなされるようです。


## Microsoft Excel

Excel Online を使用してみました。
上記Googleスプレッドシートと同様に `--` をコメントだと思ってお読みください。

```mysql
=ROUND(1.5, 0) + ROUND(2.5, 0) + ROUND(3.5, 0) + ROUND(4.5, 0) -- 14 = 2 + 3 + 4 + 5
=ROUND(1.5, 0) * ROUND(2.5, 0) * ROUND(3.5, 0) -- 24 = 2 * 3 * 4
=ROUND(-0.5, 0) + ROUND(-1.5, 0) + ROUND(-2.5, 0) -- -6 = -1 + -2 + -3
```

Excelの `ROUNDUP` , `ROUNDDOWN` 関数に関しても、試したところGoogleスプレッドシートと同様に、
負の数における切り上げ・切り捨ては絶対値を基準とした評価がされることを確認しました。


# 一覧表

|      | 1.5 + 2.5 + 3.5 + 4.5 | 1.5 * 2.5 * 3.5 | -0.5 + -1.5 + -2.5 | タイプ |
|:---  |:----:|:----:|:----:|:----:|
| 正答  |  12  |13.125| -4.5 |  -   |
| C++  |  14  |  24  |  -6  |School|
| C#   |  12  |  16  |  -4  | Even |
| Java |  14  |  24  |  -3  |School|
|Kotlin| 12.0 | 16.0 | -4.0 | Even |
|JavaScript|14|  24  |  -3  |School|
| R言語 |  12  |  16  |  -4  | Even |
|Python2|14.0 | 24.0 | -6.0 |School|
|Python3| 12  |  16  |  -4  | Even |
| NumPy| 12.0 | 16.0 | -4.0 | Even |
| PHP  |  14  |  24  |  -6  |School|
| Ruby |  14  |  24  |  -6  |School|
| Swift| 14.0 | 24.0 | -6.0 |School|
|Google Sheets|14| 24|  -6  |School|
|Microsoft Excel|14|24| -6  |School|

小数点第１位がすべて５の場合という、非常に極端な計算例ではありますが、
こうして並べてみると近い偶数へと丸めるEvenタイプがより正答に近付けるとわかります。


# 感想

2.5 を四捨五入したら 3 に、
-2.5 を四捨五入したら -3 になる気持ちでいました。

しかし実際のところ、C#, Python3, Kotlin, Java, JavaScript ではそのような挙動を見せません。
特にJavaScriptは付き合いが長かったぶん、今回、負の数の四捨五入で意外なクセを見つけて驚きました。

round, ceil, floor などはお金の計算で扱うことも多いゆえ、
その挙動の違いにはよぉ〜く気を付けたほうがいいでしょう。異なる言語への移植の際はお気を付けください！