惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
The GitHub Blog
The GitHub Blog
博客园_首页
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
腾讯CDC
I
InfoQ
量子位
J
Java Code Geeks
P
Proofpoint News Feed
有赞技术团队
有赞技术团队
Webroot Blog
Webroot Blog
Martin Fowler
Martin Fowler
D
Docker
F
Fortinet All Blogs
云风的 BLOG
云风的 BLOG
V
Vulnerabilities – Threatpost
罗磊的独立博客
P
Proofpoint News Feed
T
The Exploit Database - CXSecurity.com
Cyberwarzone
Cyberwarzone
P
Privacy & Cybersecurity Law Blog
Last Week in AI
Last Week in AI
爱范儿
爱范儿
The Hacker News
The Hacker News
S
SegmentFault 最新的问题
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
V
V2EX
Simon Willison's Weblog
Simon Willison's Weblog
AI
AI
Y
Y Combinator Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
GbyAI
GbyAI
V
Visual Studio Blog
H
Heimdal Security Blog
S
Secure Thoughts
B
Blog RSS Feed
雷峰网
雷峰网
T
Tenable Blog
C
Check Point Blog
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
About on SuperTechFans
Recent Commits to openclaw:main
Recent Commits to openclaw:main

博客园 - Enli

IOS 命令行安装备忘 IOS开发备忘 MAC 编制计划任务 弹出式窗口管理单元备忘 Wininet请求包装类简稿 自备工具库 多屏开发的备忘 界面方面的备忘 rc资源文件的中英文应用备忘 Wininet下载类初稿 Dephi调用C#编写的WebService的一些问题与解决 (转) 关于Window的快捷方式,图标缓存的清理 Delphi 备忘五 http断点续传的单元 Delphi 代码优化(转) 同名派生的应用(转) 关于delphi的程序在英文操作系统下乱码问题 - Enli - 博客园 TWebBrowser备忘 组件开发中的技巧(转)
[转载]Delphi的四舍五入函数
Enli · 2011-02-18 · via 博客园 - Enli

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
RoundEx函数华丽的变身为Trunc函数了,是不是很有趣啊,哈哈!