Roundingを理解する

# はじめに
Roundingについて説明します。Roundingとは"丸め"のことです。
ここでは次の処理によって整数を求める方法を"Rounding"とします。

- 浮動小数点数を整数にする
- 整数の除算

# Directed rounding
ある方向に沿って整数に変換します。
\- ∞ / + ∞ / 0に向かう / 0から遠ざかる それぞれの方向に応じて次の種類があります。

- R1 Round down / round towards minus infinity / floor (-∞)
- R2 Round up / round towards plus infinity / ceil (+∞)
- R3 Round towards zero / truncate / 切り捨て (->0<-)
- R4 Round away from zero / 切り上げ (<-0->)

横軸がRounding前、縦軸がRounding後のグラフを以下に示します。
斜めの点線はRoundingなしを表します。太線はRoundingありを表します。
![Fig1.JPG](https://qiita-image-store.s3.amazonaws.com/0/84205/20859816-16d9-a305-e404-c1dc953343c6.jpeg)

次に具体例を示します。

|R| example |
|:--|:--|
|R1(-∞)   | -0.5, 0, 0.5 ⇒ -1, 0, 0 |
|R2(+∞)   | -0.5, 0, 0.5 ⇒  0, 0, 1 |
|R3(->0<-) | -0.5, 0, 0.5 ⇒  0, 0, 0 |
|R4(<-0->) | -0.5, 0, 0.5 ⇒ -1, 0, 1 |

# Rounding to the nearest integer
最も近い整数に変換する方法です。四捨五入もこの方式の1つです。

隣り合う整数のちょうど中間の場合、最も近い整数が2つ存在します。
この場合、どちらの整数を選択するか決める必要があります。
例えば 0.5 に最も近い整数は 0 と 1 の 2つ あります。

中間の値をどの方向に向かって丸めるかという考え方をします。
\- ∞ / + ∞ / 0に向かう / 0から遠ざかる / 偶数に向かう / 奇数に向かう 
それぞれの方向に応じて次の種類があります。

- R5 Round half down (-∞)
- R6 Round half up (+∞)
- R7 Round half towards zero (->0<-)
- R8 Round half away from zero (四捨五入) (<-0->)
- R9 Round half to even (-> even <-)
- R10 Round half to odd (-> odd <-)

横軸がRounding前、縦軸がRounding後のグラフを以下に示します。
![1.JPG](https://qiita-image-store.s3.amazonaws.com/0/84205/7c500a60-ad71-ac84-70b4-29e33c636b7f.jpeg)

次に具体例を示します。

|R| example |
|:--|:--|
|R5(-∞)   | -0.5, 0, 0.5 ⇒ -1, 0, 0 |
|R6(+∞)   | -0.5, 0, 0.5 ⇒  0, 0, 1 |
|R7(->0<-) | -0.5, 0, 0.5 ⇒  0, 0, 0 |
|R8(<-0->) | -0.5, 0, 0.5 ⇒ -1, 0, 1 |
|R9(even)  | -1.5, -0.5, 0, 0.5, 1.5 ⇒ -2,  0, 0, 0, 2 |
|R10(odd)  | -1.5, -0.5, 0, 0.5, 1.5 ⇒ -1, -1, 0, 1, 1 |

Round half to even / odd には精度向上のメリットがあります。
具体例として 0.5 と 1.5 の合計を考えます。**正しい値は2.0**です。

0.5と1.5をそれぞれ四捨五入した後の合計を考えます。
0.5は1.0, 1.5は2.0になります。そのため合計は1.0+2.0=**3.0**となります。正しい値との差は1.0です。

0.5と1.5をそれぞれRound half to evenした後の合計を考えます。
0.5は0.0, 1.5は2.0になります。そのため合計は0.0+2.0=**2.0**となります。正しい値との差は0.0です。

# C
CにおけるRoundingの振る舞いをみてみましょう。
 MINGW64 の gcc で make します。

次のケースについて具体例を示します。

- 浮動小数点数を整数にする
- 整数の除算
- math.h (floor / ceil / round)

```makefile
CFLAGS=-I. -g -Wall -Werror -O0
INCS=
OBJS=test.o
LIBS=
TARGET=test

%.o: %.c $(INCS)
	$(CC) $(CFLAGS) -c -o $@ $<

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)

clean:
	rm -rf $(TARGET) *.o
```

```c:test.c
#include <stdio.h>
#include <math.h>

int main(int argc, char* argv[])
{
  printf("casting\n");
  printf("  (int) 1.0 = %+d\n", (int) 1.0);
  printf("  (int) 0.9 = %+d\n", (int) 0.9);
  printf("  (int) 0.5 = %+d\n", (int) 0.5);
  printf("  (int) 0.1 = %+d\n", (int) 0.1);
  printf("  (int) 0.0 = %+d\n", (int) 0.0);
  printf("  (int)-0.1 = %+d\n", (int)-0.1);
  printf("  (int)-0.5 = %+d\n", (int)-0.5);
  printf("  (int)-0.9 = %+d\n", (int)-0.9);
  printf("  (int)-1.0 = %+d\n", (int)-1.0);
  
  printf("division\n");
  printf("   10/10    = %+d\n",  10/10);
  printf("    9/10    = %+d\n",   9/10);
  printf("    5/10    = %+d\n",   5/10);
  printf("    1/10    = %+d\n",   1/10);
  printf("    0/10    = %+d\n",   0/10);
  printf("   -1/10    = %+d\n",  -1/10);
  printf("   -5/10    = %+d\n",  -5/10);
  printf("   -9/10    = %+d\n",  -9/10);
  printf("  -10/10    = %+d\n", -10/10);
  
  printf("ceiling\n");
  printf("  ceil( 1.0) = %+lf\n", ceil( 1.0));
  printf("  ceil( 0.9) = %+lf\n", ceil( 0.9));
  printf("  ceil( 0.5) = %+lf\n", ceil( 0.5));
  printf("  ceil( 0.1) = %+lf\n", ceil( 0.1));
  printf("  ceil( 0.0) = %+lf\n", ceil( 0.0));
  printf("  ceil(-0.1) = %+lf\n", ceil(-0.1));
  printf("  ceil(-0.5) = %+lf\n", ceil(-0.5));
  printf("  ceil(-0.9) = %+lf\n", ceil(-0.9));
  printf("  ceil(-1.0) = %+lf\n", ceil(-1.0));
  
  printf("flooring\n");
  printf("  floor( 1.0) = %+lf\n", floor( 1.0));
  printf("  floor( 0.9) = %+lf\n", floor( 0.9));
  printf("  floor( 0.5) = %+lf\n", floor( 0.5));
  printf("  floor( 0.1) = %+lf\n", floor( 0.1));
  printf("  floor( 0.0) = %+lf\n", floor( 0.0));
  printf("  floor(-0.1) = %+lf\n", floor(-0.1));
  printf("  floor(-0.5) = %+lf\n", floor(-0.5));
  printf("  floor(-0.9) = %+lf\n", floor(-0.9));
  printf("  floor(-1.0) = %+lf\n", floor(-1.0));
  
  printf("rounding(Round half away from zero)\n");
  printf("  round( 1.0) = %+lf\n", round( 1.0));
  printf("  round( 0.9) = %+lf\n", round( 0.9));
  printf("  round( 0.5) = %+lf\n", round( 0.5));
  printf("  round( 0.1) = %+lf\n", round( 0.1));
  printf("  round( 0.0) = %+lf\n", round( 0.0));
  printf("  round(-0.1) = %+lf\n", round(-0.1));
  printf("  round(-0.5) = %+lf\n", round(-0.5));
  printf("  round(-0.9) = %+lf\n", round(-0.9));
  printf("  round(-1.0) = %+lf\n", round(-1.0));
  
  return 0;
}
```

```bash:console
$ gcc --version
gcc.exe (Rev2, Built by MSYS2 project) 6.2.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ make && ./test.exe
cc -I. -g -Wall -Werror -O0 -c -o test.o test.c
cc -I. -g -Wall -Werror -O0 -o test test.o
casting
  (int) 1.0 = +1
  (int) 0.9 = +0
  (int) 0.5 = +0
  (int) 0.1 = +0
  (int) 0.0 = +0
  (int)-0.1 = +0
  (int)-0.5 = +0
  (int)-0.9 = +0
  (int)-1.0 = -1
division
   10/10    = +1
    9/10    = +0
    5/10    = +0
    1/10    = +0
    0/10    = +0
   -1/10    = +0
   -5/10    = +0
   -9/10    = +0
  -10/10    = -1
ceiling
  ceil( 1.0) = +1.000000
  ceil( 0.9) = +1.000000
  ceil( 0.5) = +1.000000
  ceil( 0.1) = +1.000000
  ceil( 0.0) = +0.000000
  ceil(-0.1) = -0.000000
  ceil(-0.5) = -0.000000
  ceil(-0.9) = -0.000000
  ceil(-1.0) = -1.000000
flooring
  floor( 1.0) = +1.000000
  floor( 0.9) = +0.000000
  floor( 0.5) = +0.000000
  floor( 0.1) = +0.000000
  floor( 0.0) = +0.000000
  floor(-0.1) = -1.000000
  floor(-0.5) = -1.000000
  floor(-0.9) = -1.000000
  floor(-1.0) = -1.000000
rounding(Round half away from zero)
  round( 1.0) = +1.000000
  round( 0.9) = +1.000000
  round( 0.5) = +1.000000
  round( 0.1) = +0.000000
  round( 0.0) = +0.000000
  round(-0.1) = -0.000000
  round(-0.5) = -1.000000
  round(-0.9) = -1.000000
  round(-1.0) = -1.000000
```

# Intel processor
Intel processorは次のregisterでRoundingを指定します。

- x87 FPU control register (bits 10 and 11)
- The MXCSR register (bits 13 and 14)

この2bitをRCと表記します。

| RC | Description |
|:--|:--|
|00b|Round half to even|
|01b|Round down / round towards minus infinity / floor|
|10b|Round up / round towards plus infinity / ceil|
|11b|Round towards zero / truncate|

四捨五入(Round half away from zero)がないことがわかります。

SSE4.1から**ROUNDSD** instructionが使用できます。
ROUNDSDはRC指定に従ってdoubleをRoundingします。
このinstructionは直接RCが指定できるため、MXCSRのRCを変更する必要がありません。

## Round half away from zero(四捨五入)の計算
Round half away from zeroは次のように ±0.5 をした後に Round towards zero(切り捨て)
することで求めることができます。

```c:test.c
#include <stdio.h>

int round_half_away_from_zero(double x)
{
  double y;
  
  if (0 < x) {
    y = x + 0.5;
  } else if (x < 0) {
    y = x - 0.5;
  } else {
    y = x; /* x = 0 */
  }
  
  return (int) y; /* round towards zero; truncate */
}

int main(int argc, char* argv[])
{
  printf("round_half_away_from_zero( 1.0) = %+d\n", round_half_away_from_zero( 1.0));
  printf("round_half_away_from_zero( 0.9) = %+d\n", round_half_away_from_zero( 0.9));
  printf("round_half_away_from_zero( 0.5) = %+d\n", round_half_away_from_zero( 0.5));
  printf("round_half_away_from_zero( 0.1) = %+d\n", round_half_away_from_zero( 0.1));
  printf("round_half_away_from_zero( 0.0) = %+d\n", round_half_away_from_zero( 0.0));
  printf("round_half_away_from_zero(-0.1) = %+d\n", round_half_away_from_zero(-0.1));
  printf("round_half_away_from_zero(-0.5) = %+d\n", round_half_away_from_zero(-0.5));
  printf("round_half_away_from_zero(-0.9) = %+d\n", round_half_away_from_zero(-0.9));
  printf("round_half_away_from_zero(-1.0) = %+d\n", round_half_away_from_zero(-1.0));
  
  return 0;
}
```

```bash:console
$ make && ./test.exe
cc -I. -g -Wall -Werror -O0 -c -o test.o test.c
cc -I. -g -Wall -Werror -O0 -o test test.o
round_half_away_from_zero( 1.0) = +1
round_half_away_from_zero( 0.9) = +1
round_half_away_from_zero( 0.5) = +1
round_half_away_from_zero( 0.1) = +0
round_half_away_from_zero( 0.0) = +0
round_half_away_from_zero(-0.1) = +0
round_half_away_from_zero(-0.5) = -1
round_half_away_from_zero(-0.9) = -1
round_half_away_from_zero(-1.0) = -1
```

横軸(x)がRounding前、縦軸(y)がRounding後のグラフを以下に示します。
斜めの点線は round towards zero前のグラフです。太い点線はround towards zero後のグラフです。

![1.JPG](https://qiita-image-store.s3.amazonaws.com/0/84205/0bcbd684-a892-a96f-fe41-50c594531190.jpeg)