zoukankan      html  css  js  c++  java
  • Delphi中的四舍五入函数

    一、Delphi中的四舍五入法
        四舍五入是一种应用非常广泛的近似计算方法,针对不同的应用需求,其有算术舍入法和银行家舍入法两种。
        所谓算术舍入法,就是我们通常意义上的四舍五入法。其规则是:当舍去位的数值大于等于5时,在舍去该位的同时向前位进一;当舍去位的数值小于5时,则直接舍去该位。
        所谓银行家舍入法,其实质是一种四舍六入五留双(又称四舍六入五奇偶)法。其规则是:当舍去位的数值小于5时,直接舍去该位;当舍去位的数值大于等于6时,在舍去该位的同时向前位进一;当舍去位的数值等于5时,如果前位数值为奇,则在舍去该位的同时向前位进一,如果前位数值为偶,则直接舍去该位。
        综上所述,两种舍入法所得结果不尽一致,因此在使用时必须根据实际需要加以区别。否则会出现一些莫明其妙的偏差。
        二、Delphi中的四舍五入函数
        众所周知,Delphi中有一个四舍五入取整函数Round。但它是按银行家舍入法的规则实施舍入操作的,Delphi中没有按算术舍入法规则实施舍入操作的四舍五入取整函数。
    在Delphi中使用四舍五入函数一直是使用Round,可是有时候发现,使用它得到的答案与我们预期的会不太一样。
    举例:
    i := Round(11.5)    结果: i=12
    i := Round(10.5)    结果: i=10
    是的,按照我们的预期,第二个函数应该返回11才对,可是,为什么会这样呢?
    对于XXX.5的情况,整数部分是奇数,那么会Round Up,偶数会Round Down。难道是Delphi的bug?
    No!! 让我们看看<<Pascal精要>>上的一句话:
    "在最近版本的Delphi Pascal 编译器中,Round 函数是以 CPU 的 FPU (浮点部件) 处理器为基础的。这种处理器采用了所谓的 "银行家舍入法",即对中间值 (如 5.5、6.5) 实施Round函数时,处理器根据小数点前数字的奇、偶性来确定舍入与否,如 5.5 Round 结果为 6,而 6.5 Round 结果也为6, 因为 6 是偶数"。
    Round函数其实使用的银行家算法进行运算的,统计学上一般也是使用这种算法的,这比我们传统的四舍五入方法要科学,可是,如果我们要使用传统的四舍五入的方法,该如何解决呢?
    有人是这样解决的,给10.5加上一个很微小的数值,再调用Round函数,这样在不影响精度的同时,就得到了正确的结果,貌似不错,可这始终是治标不治本的方法,有没有更正统的解决方法呢?
    在网上又搜到了一个函数:
    function DoRound(Value: Extended): Int64;
      procedure Set8087CW(NewCW: Word);
      asm
        MOV Default8087CW,AX
        FNCLEX
        FLDCW Default8087CW
      end;
    const
      RoundUpCW = $1B32;
    var
      OldCW : Word;
    begin
      OldCW := Default8087CW;
      try
        Set8087CW(RoundUpCW);
        Result := Round(Value);
      finally
        Set8087CW(OldCW);
      end;
    end;
    先解释一下8087CW, 全称是8087 control word。它是CPU中浮点单元(FPU)控制器控制字的值,设置8087CW,会改变FPU的精度,舍入模式,以及运算出错时是否产生异常。
    上面程序的思路很简单,就是先保存8087CW,然后设置它为Round Up,这样偶数时就不会Round Down了,最后再还原8087CW。
    其实上面的函数还可以简化,因为System单元里已经提供了Set8087CW的实现,所以程序简化为
    function DoRound(Value: Extended): Int64;
    const
      RoundUpCW = $1B32;
    var
      OldCW : Word;
    begin
      OldCW := Default8087CW;
      try
        Set8087CW(RoundUpCW);
        Result := Round(Value);
      finally
        Set8087CW(OldCW);
      end;
    end;

    到这里为止,这篇文章可以告一段落了,可是,最后才发现其实Borland早就想到我们会遇到这样的问题,想到我们需要定制FPU的舍入模式,所以它提供了现成的函数供我们使用。在Math单元里,有一个SetRoundMode函数。于是我们可以做一下封装:
    function RoundEx(Value: Extended; RoundMode: TFPURoundingMode = rmUp): Int64;
    var
      RM: TFPURoundingMode;
    begin
      RM := GetRoundMode;
      try
        SetRoundMode(RoundMode);
        Result := Round(Value);
      finally
        SetRoundMode(RM);
      end;
    end;

    举例:
    i := RoundEx(11.5)    结果: i=12
    i := RoundEx(10.5)    结果: i=11
    嗯,这样对了吧,如果我设置成其它RoundMode会怎样呢?
    举例:
    i := RoundEx(11.5, rmTruncate)    结果: i=11

    i := RoundEx(10.5, rmTruncate)    结果: i=10


  • 相关阅读:
    Android 3.0 r1 API中文文档(108) —— ExpandableListAdapter
    Android 3.0 r1 API中文文档(113) ——SlidingDrawer
    Android 3.0 r1 API中文文档(105) —— ViewParent
    Android 中文 API (102)—— CursorAdapter
    Android开发者指南(4) —— Application Fundamentals
    Android开发者指南(1) —— Android Debug Bridge(adb)
    Android中文API(115)——AudioFormat
    Android中文API(116)——TableLayout
    Android开发者指南(3) —— Other Tools
    Android中文API (110) —— CursorTreeAdapter
  • 原文地址:https://www.cnblogs.com/xieyunc/p/9126539.html
Copyright © 2011-2022 走看看