This project is archived and is in readonly mode.
[PATCH] Improvements in number_helper.rb
Reported by Bernardo Padua | March 19th, 2010 @ 09:40 PM | in 3.0.2
Hi all,
I've made several small refactorings and added some new
functionality to the existing number helpers (especially
number_to_human_size
and
number_with_precision
). And added a new one,
number_to_human
. All is well tested and documented.
I've put a considerable amount of thought in every change, I think
it is "ready to commit", but please review and suggest possible
improvements.
Rationale
What I needed to do was print large numbers in a rounded and
readable way for human reading (instead of printing 10,232,423,423,
printing "10.2 billion"). I thought Rails'
NumberHelper
would rescue me, but then I saw
#number_to_human_size
would only print file size. I
though of throwing in a quick helper, if Rails would only help me
round numbers to a certain amount of significant digits with
number_with_precision
, but then I was upset to
discover what rails called precision (rounding the number
of fractional digits) was far from precise/correct, according to
the standard math definition of the name precision (the
number of significant digits in a number - see what wikipedia
says ).
So I reached the conclusion Rails could do better, and decided to add this functionality.
Improvements
First I changed number_with_precision
by adding the
:significant
option, that if set to true
will make number_with_precision
round to the number
with significant digits:
number_with_precision(3.1415, :precision => 2) # => "3.14"
number_with_precision(3.1415, :precision => 2, :significant => true) # => "3.2"
number_with_precision(1232.2342, :precision => 2) # => "1232.23"
number_with_precision(1232, :precision => 2, :significant => true) # => "1200"
number_with_precision(12, :precision => 4) # => "12.0000"
number_with_precision(12, :precision => 4, :significant => true) # => "12.00"
Then I noticed number_to_human_size
stripped
unsignificant zeros after the decimal period by default, and
thought this ought to be in number_with_precision
, and
not there. So I refactored.
number_with_precision(12, :precision => 4, :strip_unsignificant_zeros => true) # => "12"
number_with_precision(12, :precision => 4, :significant => true, :strip_unsignificant_zeros => true) # => "12"
Then I decided number_to_human_size
should use
:strip_unsignificant_zeros => true
by default. It
makes no sense to use precision => 1
and display
"1.3 KB" and "657.6 Kb" (2 digits versus 4 digits). It would be
more reasonable (and less overwhelming to the end user) to display
"1.3 KB" and "650 KB" (using :precision => 2,
:strip_unsignificant_zeros => true
). We are humans and
our brains can't process too many digits (we always round). I know
this can lead to some controversy, since it changes previous
behavior. But come on, Rails is opinionated, its view code (won't
break anything) and it is the right thing to do. In the paranoid
scenario, we could add something to
initializers/new_rails_defaults.rb
, but I think it
would be overkill (in this case).
number_to_human_size(kilobytes(543.43), :precision => 2) # => "540 KB"
number_to_human_size(kilobytes(543.34), :precision => 2, :strip_unsignificant_zeros => false) # => "543.34 KB"
Besides that, I refactored the other number helpers to benefit
from those changes (eg.: number_with_percentage can also have
:significant => true
), while keeping their
defaults. I also made some improvements to the code (it's more
clean and rubyish now), and fixed some minor issues with helpers
not actually returning the untouched first parameter if it was not
a valid number.
number_to_human
I decided to call it number_to_human
since it is
more general than number_to_human_size
, yet similar.
It prints stuff like "1.2 million" and "3.4 trillion" by default,
but it can be easily tweaked to print anything that uses a decimal
scale, like 12 centimeters, 34 meters, 76 kilometers (No, it won't
convert ounces to pounds.).
By default I set in actionview/locale/en.yml
a list
of the decimal quantifiers:
human:
format:
delimiter: ""
precision: 3
significant: true
# Used in number_to_human_size()
storage_units:
format: "%n %u"
units:
byte:
one: "Byte"
other: "Bytes"
kb: "KB"
mb: "MB"
gb: "GB"
tb: "TB"
# Used in number_to_human()
decimal_units:
format: "%n %u"
units:
unit: ""
thousand: thousand
million: million
billion: billion
trillion: trillion
quadrillion: quadrillion
So it will do by default:
number_to_human(123456789) # => 120 million
number_to_human(123456789012, :precision => 4) # => 123.4 billion
But this can be easily customized by the user:
number_to_human(2780, :units => {:hundred => "hundred"}) # => 28 hundred
And he can also define it in his locale:
distance:
centi:
one: "centimeter"
other: "centimeters"
unit:
one: "meter"
other: "meters"
thousand:
one: "kilometer"
other: "kilometers"
billion: "gazilion-distance"
And then do:
number_to_human(2780, :units => :distance) # => 2.8 kilometers
number_to_human(523, :units => :distance) # => 520 meters
number_to_human(0.238, :units => :distance) # => 24 centimeters
number_to_human(0.238, :units => :distance) # => 24 centimeters
The possible unit quantifiers that can be used is defined in the DECIMAL_UNITS constant hash, and is currently set to:
{0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico}.freeze
Adding support for a new one (eg.: femto or quintillion) as simple as adding a new key => value pair to this hash with the proper exponent.
Patch will follow (I need the ticket number before I make the commit)
Comments and changes to this ticket
-
Bernardo Padua March 19th, 2010 @ 10:11 PM
- Assigned user set to José Valim
BTW, somebody please close #1340 as it looks like it was already fixed.
-
José Valim March 19th, 2010 @ 10:37 PM
- Assigned user cleared.
- Milestone cleared.
-
Jeremy Kemper March 19th, 2010 @ 10:44 PM
- State changed from new to open
- Assigned user set to José Valim
Great patch, Bernardo!
-
José Valim March 19th, 2010 @ 10:47 PM
- State changed from open to new
-
Bernardo Padua March 20th, 2010 @ 08:21 PM
Thanks for the nice feedback, José and Jeremy. Following some suggestions by José (exchanged in portuguese, I won't bother printing them here... :)), I refactored the patch (and also found/fixed some minor issues), here is the 2nd take. In the process I rewrote the number_helper_i18n_test.rb, since it used mocking in a very brittle way and broke completely with the slightest refactoring.
Something I forgot to write in the ticket (but it is in the docs), is that you can also supply your custom formatting for
number_to_human
:number_to_human(2000, :format => "Y%n%u", :units => {:thousand => "k"}) # => "Y2k"
Any questions/requests let me know.
-
Bernardo Padua March 20th, 2010 @ 08:44 PM
I just discovered there was a line/hack in
number_to_currency
which wasn't doing anything useful (its probably from when it didn't rely onnumber_with_precision
), so I yanked it:separator = '' if precision == 0
Now
number_to_currency
looks much better. Hope its not too late. -
Bernardo Padua March 20th, 2010 @ 09:36 PM
Ops, me one more time!
I just figured out rails 3 will have this
html_safe
thing (I wasn't aware, since I am still working with 2.3), and most of the outputs from NumberHelper were nothtml_safe
(only number_to_currency was, for some reason).I am trying to fix that, hold until I send the updated patch.
-
Bernardo Padua March 22nd, 2010 @ 07:46 PM
Hi,
I finally added support for html_safe throughout NumberHelper methods. The updated patch follows (there are two commits, one for the previous improvements, other for this).
We have to make sure the number (which may also be a string in all helpers) does not have any unsafe stuff, so it was not just a matter of adding
.html_safe
to everything that is returned. Most number helpers return thenumber
param unchanged if they are not a valid number (sometimes prepended/appended by something, like number + "%" innumber_to_percentage
), so this is where the danger lies. To accommodate that I had to be a little bit more strict when parsing the numbers in some helpers (which were not doing any parsing) and had to add a way for the helpers to throw exceptions so they could be "bubbled up" to helpers that in turn relied on other helpers (eg.:number_to_currency
relies onnumber_with_precision
).Of course it is tested. I also structured the tests a little bit more to make a few of those things more clear (methods returning number param unchanged, returning nil when number is nil, etc).
Waiting for your reviews now.
-
Repository March 22nd, 2010 @ 08:42 PM
- State changed from new to resolved
(from [75904c566e3ea475045450ba8fb1a74070a94fcb]) Adds number_to_human and several improvements in NumberHelper. [#4239 state:resolved]
Signed-off-by: José Valim jose.valim@gmail.com
http://github.com/rails/rails/commit/75904c566e3ea475045450ba8fb1a7... -
Jeremy Kemper October 15th, 2010 @ 11:01 PM
- Milestone set to 3.0.2
- Importance changed from to Low
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
<h2 style="font-size: 14px">Tickets have moved to Github</h2>
The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>
People watching this ticket
Attachments
Referenced by
- 4239 [PATCH] Improvements in number_helper.rb (from [75904c566e3ea475045450ba8fb1a74070a94fcb]) Adds nu...