任意の少数点桁で四捨五入する
87 HalfAdjust 動作確認 Delphi2007 更新日 2008/02/20(水)
2010/07/22(木)

DelphiではRoundのHELPに
少数点を四捨五入するための関数

    function RoundOff(X: Extended): Longint;

があります。
これを応用して任意の桁(整数部分でも少数点以下部分でも)で
四捨五入して上の桁に揃えてしまう関数を作りました。

────────────────────
//HELPにのっているRoundOff
function RoundOff(X: Extended): Longint; overload;
begin
  if x >= 0 then Result := Trunc(x + 0.5)
  else Result := Trunc(x - 0.5);
end;

{-------------------------------
//  RoundOff
機能:       任意の桁で四捨五入します。
引数説明:   X: 四捨五入対象
            DigitNumber: 桁数
            1:一桁目
            2:10の桁
            3:100の桁
            0:処理しない
            -1:少数点第一位(通常のRoundOffと同じ処理)
            -2:少数点第二位
            -3:少数点第三位
戻り値:     四捨五入後のX
            処理できない場合Xの値そのままが入るはず
備考:
履歴:       2001/09/04
//------------------------------}
function RoundOff(X: Extended; DigitNumber: Integer): Extended; overload;
var
  CalcDigit: Extended;
begin
  Result := X;
  if X = 0 then Exit;

  case DigitNumber of
    0: Exit;
    1..High(DigitNumber):
    begin
      CalcDigit := IntPower(10, DigitNumber);
      Result := Roundoff(X / CalcDigit) * CalcDigit;
    end;
    Low(DigitNumber)..-1:
    begin
      CalcDigit := IntPower(10, Abs(DigitNumber)-1);
      Result := Roundoff(X * CalcDigit) / CalcDigit;
    end;
  end;
end;
//------------------------------

{-------------------------------
//  IsSame
機能:       適当に小さい誤差の範囲内で
            浮動小数点値が等しいかどうかを調べる関数
引数説明:   A, B: 比較対照
            e: ±の誤差
戻り値:     true:等しい false:等しくない
備考:       Math.SameValueと同じ機能かも
履歴:       2001/09/05
//------------------------------}
function IsSame(A, B: Extended; e: Extended=0): Boolean;
begin
  Result := Abs(A-B) <= e;
end;
//------------------------------

procedure testRoundOff;
begin
  Check(True, IsSame(150,RoundOff(145, 1),0.000001));   //145を1桁目で四捨五入すると150になる
  Check(True, IsSame(150,RoundOff(154, 1),0.000001));   //154を1桁目で四捨五入すると150になる
  Check(True, IsSame(140,RoundOff(144, 1),0.000001));
  Check(True, IsSame(160,RoundOff(155, 1),0.000001));
  Check(True, IsSame(150,RoundOff(150, 1),0.000001));
  Check(True, IsSame( 10,RoundOff( 14, 1),0.000001));
  Check(True, IsSame( 10,RoundOff(  5, 1),0.000001));
  Check(True, IsSame(  0,RoundOff(  4, 1),0.000001));
  Check(True, IsSame( 20,RoundOff( 15, 1),0.000001));
  Check(True, IsSame( 10,RoundOff( 10, 1),0.000001));

  Check(True, IsSame(200,RoundOff(150, 2),0.000001));   //150を2桁目で四捨五入すると200になる
  Check(True, IsSame(100,RoundOff(140, 2),0.000001));
  Check(True, IsSame(100,RoundOff(149, 2),0.000001));   //149を2桁目で四捨五入すると100になる
  Check(True, IsSame(200,RoundOff(249, 2),0.000001));
  Check(True, IsSame(100,RoundOff( 50, 2),0.000001));

  Check(True, IsSame(1000,RoundOff(1400, 3),0.000001)); //1400を3桁目で四捨五入すると1000になる
  Check(True, IsSame(2000,RoundOff(1500, 3),0.000001));
  Check(True, IsSame(2000,RoundOff(1600, 3),0.000001));
  Check(True, IsSame(1000,RoundOff(1499, 3),0.000001));
  Check(True, IsSame(2000,RoundOff(2480, 3),0.000001));

  Check(True, IsSame(-150,RoundOff(-145, 1),0.000001)); //-145を1桁目で四捨五入すると-150になる
  Check(True, IsSame(-150,RoundOff(-154, 1),0.000001));
  Check(True, IsSame(-140,RoundOff(-144, 1),0.000001));
  Check(True, IsSame(-160,RoundOff(-155, 1),0.000001));
  Check(True, IsSame(-150,RoundOff(-150, 1),0.000001));
  Check(True, IsSame(- 10,RoundOff( -14, 1),0.000001));
  Check(True, IsSame(- 10,RoundOff(  -5, 1),0.000001));
  Check(True, IsSame(-  0,RoundOff(  -4, 1),0.000001));
  Check(True, IsSame(- 20,RoundOff( -15, 1),0.000001));
  Check(True, IsSame(- 10,RoundOff( -10, 1),0.000001));

  Check(True, IsSame(-200,RoundOff(-150, 2),0.000001));
  Check(True, IsSame(-100,RoundOff(-140, 2),0.000001));
  Check(True, IsSame(-100,RoundOff(-149, 2),0.000001));
  Check(True, IsSame(-200,RoundOff(-249, 2),0.000001));
  Check(True, IsSame(-100,RoundOff( -50, 2),0.000001));

  Check(True, IsSame(-1000,RoundOff(-1400, 3),0.000001));
  Check(True, IsSame(-2000,RoundOff(-1500, 3),0.000001));
  Check(True, IsSame(-2000,RoundOff(-1600, 3),0.000001));
  Check(True, IsSame(-1000,RoundOff(-1499, 3),0.000001));
  Check(True, IsSame(-2000,RoundOff(-2480, 3),0.000001));

  Check(True, IsSame(0,RoundOff(0.49, -1),0.000001));   //0.49を小数点1桁目で四捨五入すると0になる
  Check(True, IsSame(1,RoundOff(0.50, -1),0.000001));   //0.50を小数点1桁目で四捨五入すると1になる
  Check(True, IsSame(1,RoundOff(1.49, -1),0.000001));
  Check(True, IsSame(2,RoundOff(1.50, -1),0.000001));
  Check(True, IsSame(1.0,RoundOff(1.04, -2),0.000001)); //1.04を小数点2桁目で四捨五入すると1.0になる
  Check(TRue, IsSame(1.1,RoundOff(1.05, -2),0.000001)); //1.05を小数点2桁目で四捨五入すると2になる

  Check(True, IsSame(1.0001,RoundOff(1.00005000, -5),0.00000001));
  Check(True, IsSame(1.0000,RoundOff(1.00004999, -5),0.000001));
  Check(True, IsSame(1.000050,RoundOff(1.00005000, -6),0.000001));
  Check(True, IsSame(1.000050,RoundOff(1.00004999, -6),0.000001));
end;

────────────────────
余談ですが、最初は
RoundOff関数を使わずに
整数のみで自作四捨五入関数を作ってみていました。

DigitNumberを何桁目を四捨五入するかという指定で
整数を以下のように処理してみています。

  if DigitNumber=1 then
  begin
    if (X mod 10) < 5 then
    begin
      Result := (X div 10) * 10;
    end else
    begin
      Result := ((X div 10)+1) * 10;
    end;
  end else
  if DigitNumber=2 then
  begin
    if (X mod 100) < (5*10) then
    begin
      Result := (X div 100) * 100;
    end else
    begin
      Result := ((X div 100)+1) * 100;
    end;
  end else
  //DigitNumber1,2,3..どれでも通用する処理
    ResultDigit := Round(IntPower(10,DigitNumber));
    TargetDigit := Round(IntPower(10,DigitNumber-1));

    if (X mod ResultDigit) < (5*TargetDigit) then
    begin
      Result := ( X div ResultDigit ) * ResultDigit;
    end else
    begin
      Result := ((X div ResultDigit)+1) * ResultDigit;
    end;
  end;

このように工夫していくと
RoundやRoundOff関数が用意されていなくても
四捨五入関数を自作することが出来ていくでしょう。


更に余談ですが四捨五入の方法として
少数点以下の桁だけになりますが
Format関数で四捨五入した実数値を文字列として返す事もできます。

    procedure Check(S1, S2: String); overload;
    begin
      if S1 <> S2 then
        ShowMessage('×');
    end;

    procedure testFormatFloat;
    begin
      Check('1',   Format('%.0f', [1.4]));
      Check('2',   Format('%.0f', [1.5]));
      Check('1',   Format('%.0f', [1.49]));
      Check('2',   Format('%.0f', [1.50]));
      Check('1.5', Format('%.1f', [1.49]));
      Check('1.5', Format('%.1f', [1.50]));
      Check('1.5', Format('%.1f', [1.45]));
      Check('1.5', Format('%.1f', [1.54]));
      Check('1.4', Format('%.1f', [1.44]));
      Check('1.6', Format('%.1f', [1.55]));

      Check('1.0001', Format('%.4f', [1.00005000]));
      Check('1.0000', Format('%.4f', [1.00004999]));
      Check('1.000',  Format('%.3f', [1.00005000]));
      Check('1.000',  Format('%.3f', [1.00004999]));
    end;

Format関数の戻り値を StrToFloat を使って変換すると
Extendet型の値を取得することができます。

参考────────────────────
FDelphi Delphi FAQ: 小数の切り捨て・切り上げ・四捨五入
http://delfusa.main.jp/delfusafloor/archive/www.nifty.ne.jp_forum_fdelphi/faq/00126.htm
       四捨五入はRoundOffを使う。Roundだと四捨五入にならないので記載が間違っている。

小数点以下の切捨て、切り上げ、四捨五入 - Delphi - sciencesystemグループ
http://sciencesystem.g.hatena.ne.jp/bbs/2/24?fromtreemode=1
       FDelphiと同じ問題

About Delphi Tips - 計算
http://www2.big.or.jp/~osamu/Delphi/Tips/key.cgi?key=41#0073.txt

Delphi AcidF loor
http://www.wwlnk.com/boheme/delphi/vbtodel/daf0560.html
       About Delphi と同じ実装

くろねこ研究所 - [Delphi] 切り捨て、四捨五入、切り上げ
http://www.blackcatlab.com/article.php/ProgramingFAQ_del0044
       詳しくてGood!