Ruby number_to_currency displays a totally wrong number!
Greetings! I’m an old Delphi programmer making the leap to the Mac, Ruby, Rails, and web programming in general. I’m signed up for the Advanced Rails workshop at the end of the month. In the meantime, I’ve been working on porting a mission-critical (of course) app from Delphi to RAILS. It feels like I’ve spent most of the past year with my head buried in a book or podcast.
Right now, I’ve hit a major issue and I’m tearing my hair out. I literally don’t know where to go with this, I desperately don’t want to deploy with this bug, and I’m feeling a bit frantic. (The company database is currently running on an a开发者_JS百科ncient XP box that’s looking rustier by the day.)
So, I set up a test database that shows the problem. I’m running:
OS/X 10.6.3
Rails 2.3.5
ruby 1.8.7 (2009-06-08 patchlevel 173) [universal-darwin10.0]
MySQL 5.1.38-log via socket
MySQL Client Version 5.1.8
ActiveRecord::Schema.define(:version => 20100406222528) do
create_table “money”, :force => true do |t|
t.decimal “amount_due”, :precision => 10, :scale => 2, :default => 0.0
t.decimal “balance”, :precision => 10, :scale => 2, :default => 0.0
t.text “memofield”
t.datetime “created_at”
t.datetime “updated_at”
end
The index view is right out of the generator, slightly modified to add the formatting that's breaking on me.
Listing money
<table>
<tr>
<th>Amount</th>
<th>Amount to_s </th>
<th>Balance to $</th>
<th>Balance with_precision </th>
<th>Memofield</th>
</tr>
<% @money.each do |money| %>
<tr>
<td><%=h money.amount_due %></td>
<td><%=h money.amount_due.to_s(‘F’) %></td>
<td><%=h number_to_currency(money.balance) %></td>
<td><%=h number_with_precision(money.balance, :precision => 2) %></td>
<td><%=h money.memofield %></td>
<td><%= link_to ‘Show’, money %></td>
<td><%= link_to ‘Edit’, edit_money_path(money) %></td>
<td><%= link_to ‘Destroy’, money, :confirm => ‘Are you sure?’, :method => :delete %></td>
</tr> *<% end %> *</table>
<%= link_to ‘New money’, new_money_path %>
This seemed to work pretty well. Then I started testing with production data and hit a major problem with number_to_currency.
The number in the database is: 10542.28, I verified it with the MySQL Query Browser. RAILS will display this as 10542.28 unless I call number_to_currency, then that number is displayed as: $15422.80
The error seems to happen with any number between 10,000.00 and 10,999.99 So far, I haven’t seen it outside of that range, but I obviously haven’t tested everything.
I guess my workaround is to remove number_to_currency, but that leaves the views looking really sloppy and unprofessional. The formatting is messed up, things don’t line up properly and I can’t force the display to 2 decimal places.
I’m seriously hoping there is an easy fix for this. I can’t imagine this being a widespread problem. It would affect so many people that someone would have fixed it! But I don’t know where to go from here.
I’d desperately like some help.
(Later – number_with_precision fails the same way number_to_currency does.)
Sue Petersen
Until you find a fix, you can make your own :)
def formatted_number(n)
a,b = sprintf("%0.2f", n).split('.')
a.gsub!(/(\d)(?=(\d{3})+(?!\d))/, '\\1,')
"$#{a}.#{b}"
end
DATA.each do |n|
puts formatted_number(n)
end
__END__
1
1.1
1.11
11
11.1
11.11
111
111.1
111.11
1111
1111.1
1111.11
11111
11111.1
11111.11
111111
111111.1
111111.11
1111111
1111111.1
1111111.11
11111111
11111111.1
11111111.11
111111111
111111111.1
111111111.11
Output
$1.00
$1.10
$1.11
$11.00
$11.10
$11.11
$111.00
$111.10
$111.11
$1,111.00
$1,111.10
$1,111.11
$11,111.00
$11,111.10
$11,111.11
$111,111.00
$111,111.10
$111,111.11
$1111,111.00
$1111,111.10
$1111,111.11
$1,1111,111.00
$1,1111,111.10
$1,1111,111.11
$11,1111,111.00
$11,1111,111.10
$11,1111,111.11
Maybe related to this:
rails number_to_currency bug?
class Float
def to_currency
a,b = sprintf("%0.2f", self).split('.')
a.gsub!(/(\d)(?=(\d{3})+(?!\d))/, '\\1,')
"$#{a}.#{b}"
end
end
class Integer
def to_currency
a,b = sprintf("%0.2f", self).split('.')
a.gsub!(/(\d)(?=(\d{3})+(?!\d))/, '\\1,')
"$#{a}.#{b}"
end
end
x = [100.236, 2, 123456789.1]
x.each do | n |
puts x.to_currency
end
# Results
# $100.24
# $2.00
# $123,456,789.10
I've added a to_currency method to the Float and Integer classes. It returns the number as a formatted string.
Another (more fancy) solution that might help you until the bug is resolved
def formatted_number(n, options={})
options = {
:precision => 2,
:separator => '.',
:delimiter => ',',
:format => "$%s"
}.merge(options)
a,b = sprintf("%0.#{options[:precision]}f", n).split('.')
a.gsub!(/(\d)(?=(\d{3})+(?!\d))/, "\\1#{options[:delimiter]}")
sprintf(options[:format], "#{a}#{options[:separator]}#{b}")
end
DATA.each do |n|
# default $1,234.56
puts formatted_number(n)
# precise $0.12345
puts formatted_number(n, :precision => 5)
# European 1.234,56 EUR
puts formatted_number(n, :separator => ',', :delimiter => '.', :format => '%s EUR')
# line break
puts ''
end
__END__
5.5966
52.5344
3021.565
34809.48
25923.68342
Output
$5.60
$5.59660
5,60 EUR
$52.53
$52.53440
52,53 EUR
$3,021.57
$3,021.56500
3.021,57 EUR
$34,809.48
$34,809.48000
34.809,48 EUR
$25,923.68
$25,923.68342
25.923,68 EUR
@smotchkkiss I tried both suggestions. They both did the same thing number_to_currency does on my system. I suspect this is something in the Ruby version I'm running.
I spent most of the day trying to get a newer version of Ruby installed on my system, and finally gave up and did a complete system restore from Time Machine. I'm still learning the Mac's quirks and I really managed to mess up my RubyGems installation.
@Andrew Grimm I would love to leave this as a comment. I see no way to do so (running Snow Leopard, Firefox w NoScript). I spent at least 30 minutes going thru the FAQ, trying to find a way to leave a comment instead of an answer. No Go.
And this is an answer, at least a partial answer.
I managed to load a newer version of Ruby (ruby 1.8.7 (2010-01-10 patchlevel 249)), and the problem disappeared. It is apparently a bug in Ruby. Now I just have to figure out how to install a newer version of Ruby into system on my MacBook and the 2 minis I've been using as test servers.
I ended up loading the rvm gem on all of my Macs, and I'm now running ruby 1.8.7 p249. That solved the problem for me.
I did verify the bug in Ruby, and I found this for more info:
http://www.ruby-forum.com/topic/189053#827091
cihaks - It sounds like the temporary patch they suggested is very similar to what you're doing. In any case, thanks for the post and the info. :-)
I ran into the same problem. It seems like the issue is in the translation of a Big Decimal to a float. I changed one line in the formatted_number method and was able to get the number to appear as it should:
a,b = sprintf("%0.#{options[:precision]}f", n).split('.')
was changed to:
a,b = sprintf("%0.#{options[:precision]}f", n.to_s.to_f).split('.')
Converting to a string first, and then to a float did the trick. It's hokey but until the underlying issue gets resolved it will work for us.
精彩评论