计算机中的舍入问题,其实算是个老生长谈的问题。最初是在VB中产生过这种疑问,后来知道这是IEEE的一个标准,因此也成为各种语言通用的标准,说起来也是个“国际惯例”哩。这种舍入方法所采取的是四退六进五取偶,与国内传统的四舍五入还是有很大区别的,还有人专门写了一个四舍五入的程序,具体解释可参见百度百科等相关资讯,记得第一次接触这种舍入法应该是在大二的第一次物理实验课上,那位有点老学究模样的诸琢雄老师(可没有贬低的意思,过了五六年还能记得他的名字说明偶很尊敬他滴,据说他还是本校超级名捕之一呢)提到了这种舍入法,他称为“奇进偶不进”,又过了没多久,在测量学课上,黄晓时老师也讲到了,只不过称其为“单进双不进”,原理其实都是一样的。
为什么想起提到这个问题呢,是因为在写平差程序时遇到一个问题,有点棘手,现在也没想出来为什么,各位可以试一下。
double s = Math.Round((4.922F+4.921F)/2, 3); //结果是4.921
double s = Math.Round(4.9215F, 3); //结果是4.922
double s = Math.Round((4.922+4.921)/2, 3); //结果是4.922
double s = Math.Round(4.9215, 3); //结果是4.922
看出来了吗?就因为single和double的类型问题,所以舍入问题都会有些小bug,可是我的运算位数根本不需要double这么大的,难道每一次都要把single转换成double吗?好吧,就算我不厌其烦的转换,还是会出现问题,请看
double d1 = Math.Round((Convert.ToDouble(4.922F) + Convert.ToDouble(4.921F)) / 2, 3); //结果还是4.921
这回傻了吧?我也傻了,还好我的平差等级不高,进到哪一位问题都不大,否则就挂了。。。
或许这是个低级的小问题,但我确实没有太好的对策,有一种解决方法是每次都加0.0001之类的,例如
double s = Math.Round((4.922F + 4.921F) / 2 + 0.0001, 3); //结果是4.922
这样看MS是对了,可以写个类似的函数,每次都加上这么一个数,但如果写个函数每个都加这个0.0001,那么则会出现下列情况
double s = Math.Round((4.9214F + 4.9214F) / 2 + 0.0001, 3); //结果是4.922
看来这种想法有点问题,继续修改,发现问题出在把那个1加在了重要的进位判断的位数上,那么如果把0.0001的小数点再左移一位呢:
double s = Math.Round((4.922F + 4.921F) / 2 + 0.00001, 3); //结果是4.922
double s = Math.Round((4.9214F + 4.9214F) / 2 + 0.00001, 3); //结果是4.921
这回终于解决了,可以动手写个函数,既实现四舍五入,又可以解决single与double的这个小bug,当然,我想应该也可以把这个0.00001写作一个极小的常数,没有动手做过,只是猜想的
internal double ChinaRound(double value, int decimals)
{
return Math.Round(value + 1 / Math.Pow(10, decimals + 2), decimals, MidpointRounding.AwayFromZero);
}
这里有个问题需要注意一下,就是负数的四舍五入问题,例如-1.5是该舍入到-1还是到-2呢?我这里采用的是-2,其实对于这种中间数舍入到哪一个问题都不大,关键是要做到统一,如果你想舍入到-1,恐怕还有另外写个分支结构,网上很多,有一个是
if (value < 0)
{
return Math.Round(value + 5 / Math.Pow(10, decimals + 1), decimals, MidpointRounding.AwayFromZero);
}
偶没测试过,有兴趣的人自己弄一下吧,如果哪位大侠有更好的解决方法,还望不吝赐教,多谢了