2008-03-22
Trick: Rails里的number_with_precision
关键字: ruby, rails, number_with_precision, number_to_currency缘起: 很早以前碰到一个需求,实现一个四舍五入(round)的全局HelperMethod,并不难,写出来以后就放在哪里了.而Rails直接提供了一个number_to_currency方法可以方便的在rhtml中将数字显示为CurrencyString,问题来了
helper.number_to_currency(1234567890.50) # => $1,234,567,890.50如果对这个数字31.825执行转换呢?
helper.number_to_currency(31.825) #=> "$31.82"结果不对呀,应该是"$31.83"才对,继续对数字32.825执行转换
helper.number_to_currency(32.825) #=> "$32.83"
这次结果又是对了,怪不怪?
我当前正在使用的平台是Ubunt 7.10, ruby 1.8.6, rails 2.02
ActionView::Helpers::NumberHelper#number_to_currency方法中的round过程其实是通过ActionView::Helpers::NumberHelper#number_with_precision方法完成的
方法实现非常简单,仅仅用到了String的格式化输出
"%01.#{precision}f" % numberRails文档上的example写到number_with_precision(111.2345) # => 111.235可实际执行结果并不是这样
helper.number_with_precision(111.2345) #=> "111.234"
在官方的Rails Trac中也有人提到这个问题,且看来问题由来已久,见[trick 10090],[trick 8275]
对问题的看法大概分为两个方面,一方认为文档写错了,修改文档.另一方认为的确是计算结果不对,但不是rails的错,错在ruby的String格式化输出,而深入研究则发现这个问题源自C语言的格式化字符串上:
$ gcc -v
gcc version 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)
# 编译下面C代码
# include <stdio.h>
main()
{
printf("%01.2f\n", 31.825);
printf("%01.2f\n", 32.825);
}
# 我自己编译后输出结果的确是如此:
31.82
32.83有人提出用BigDecimal替换Float数据类型"%01.20f" % BigDecimal.new("31.825") #=> "31.82"
"%01.20f" % BigDecimal.new("32.825") #=> "32.82"在我的平台上还不如用Float来计算,用Float至少我还有对的时候,但是其他平台上难说,因为String#%方法是C语言实现,具有平台依赖性.
其实终端控制台Console下的Bash本身也支持printf输出,且计算结果没有任何问题!
$ bash -version GNU bash, version 3.2.25(1)-release (i486-pc-linux-gnu) Copyright (C) 2005 Free Software Foundation, Inc. $ printf "%01.2f\n" 31.825 31.83 $ printf "%01.2f\n" 32.825 32.83
我必须在rails中处理一些严肃的计算任务,必须严格按照正确的四舍五入计算方法处理并显示结果,比如实时显示税率计算结果等
有人提出是因为Float本身精度限制
目前我用了这样的一个算法:
"%01.20f" % 31.825 #=> "31.82499999999999928946"只要计算过程中给初始数字加权一个tiny数即可绕过Float的精度问题,为了尽量不影响精度,这个tiny数我取0.1的(precision^precision)次方
def number_with_precision(number, precision=2)
"%01.#{precision}f" % (number + 0.1 ** (precision ** precision))
end
number_with_precision(31.825) #=> "31.83"
的确是个办法,但是有一点小问题,如果我想要6位精度,那么number_with_precision(31.0000005,6) #=> "31.000000"加权计算的tiny数太小了,不足以影响Float精度,但是太大初始数可能会不对,如何聪明的判断加权的tiny数是刚好呢?
目前我用了这样的一个算法:
def number_with_precision(number, precision=3)
"%01.#{precision}f" % ((Float(number)*(10**precision)).round.to_f/10**precision)
rescue
number
end
number_with_precision(31.825, 2) #=> "31.83"
number_with_precision(32.825, 2) #=> "32.83"
number_with_precision(32.0000005, 6) #=> "31.000001"这个方法实现比较繁琐,先改变初始数小数点位置,后移precision个位置,然后用round四舍五入掉小数点后一位,在改变小数点尾数到初始状态,最后结果再用String格式化输出,保证小数点后精度的有效位.不知还有没有比较方便简洁一点的方法?
评论
lgn21st
2008-03-25
今天早上真兴奋,开心~~~
通过元一同学的大力帮助,测试,修改,这个number_with_precision已经被Rails-Core正式接受了,太棒了,谢谢所有人帮助测试~~~
ChangeSet: http://dev.rubyonrails.org/changeset/9086
Ticket: http://dev.rubyonrails.org/ticket/11409
再接再厉,努力学习,打好基础,为社区多多作贡献!
通过元一同学的大力帮助,测试,修改,这个number_with_precision已经被Rails-Core正式接受了,太棒了,谢谢所有人帮助测试~~~
ChangeSet: http://dev.rubyonrails.org/changeset/9086
Ticket: http://dev.rubyonrails.org/ticket/11409
再接再厉,努力学习,打好基础,为社区多多作贡献!
发表评论
- 浏览: 41836 次
- 性别:

- 来自: 上海

- 详细资料
搜索本博客
最近加入圈子
最新评论
-
关于如何解决上SourceForg ...
mark
-- by insiku -
MacOSX: 安装MySQL和Post ...
买了MacBook拉?
-- by chenk85 -
MacOSX: 安装MySQL和Post ...
我个人偏向使用二进制预编译包,省事,不过我没有用过这个,想问一句,如果用http ...
-- by lgn21st -
关于如何解决上SourceForg ...
爬墙。。。。tor+无边。。。。强大~
-- by timedifier -
MacOSX: 安装MySQL和Post ...
直接用现成的安装包多省力阿... http://www.postgresqlf ...
-- by csr2000






评论排行榜