This project is archived and is in readonly mode.

#4239 ✓resolved
Bernardo Padua

[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

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>

Referenced by

Pages