LoginSignup
10
9

More than 5 years have passed since last update.

Roundingを理解する

Posted at

はじめに

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

次に具体例を示します。

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

次に具体例を示します。

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)
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
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;
}
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(切り捨て)
することで求めることができます。

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;
}
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

10
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9