33

I need a way to format numbers. I stored some numbers in my DB table, e.g. 12500, and would like to print them in this format 12 500 (so there is a space every 3 digits). Is there an elegant way to do this?

sawa
  • 165,429
  • 45
  • 277
  • 381
user1946705
  • 2,858
  • 14
  • 41
  • 58

12 Answers12

39

There is no built in way to it ( unless you using Rails, ActiveSupport Does have methods to do this) but you can use a Regex like

formatted_n = n.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
Community
  • 1
  • 1
loosecannon
  • 7,683
  • 3
  • 32
  • 43
  • 2
    that will add commas, not spaces btw – loosecannon Jun 23 '11 at 18:39
  • 5
    Just replace the ampersand with a space (`\&,` with `\& `) and it'll work. – Dylan Markow Jun 23 '11 at 18:40
  • 1
    Thank you guys for quick help. But, tell the truth, I thought that exist some nicer way (e.g. something like [this function](http://php.net/manual/en/function.number-format.php) in PHP). But how I am saying, thanks for solution! – user1946705 Jun 23 '11 at 18:46
  • 5
    Here's a modification that handles negative numbers too: `n.to_s.reverse.gsub(/...(?!-)(?=.)/,'\&,').reverse`. For example: "-174", not "-,174". – Steph Sharp Aug 31 '13 at 10:13
  • The Rails / ActiveSupport method mentioned is [Numeric#to_formatted_s](http://api.rubyonrails.org/classes/Numeric.html#method-i-to_formatted_s) – tight Aug 06 '14 at 08:07
  • Here's a variant, to avoid the two `reverse`'s: `str=n.to_s; f=str.size%3; str[0,f] << str[f..-1].gsub(/.../, ' \&')`. – Cary Swoveland Nov 01 '14 at 03:54
  • Is there a way to deal with float numbers? The method above gaves me the following result: "1,231,213,213,213,.33" - with an extra comma at then end, together with the dot separator – mickael Mar 17 '17 at 11:06
  • @mickael You could change the `.`'s to match only numbers instead of anything. I don't recall the exact syntax of ruby regexes these days but something like `/[0-9]{3}(?=[0-9])/` or along those lines. See steenslag's answer below also – loosecannon Apr 03 '17 at 23:34
23

Activesupport uses this regexp (and no reverse reverse).

10000000.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1 ") #=> "10 000 000"
steenslag
  • 79,051
  • 16
  • 138
  • 171
13

Here's another method that is fairly clean and straightforward if you are dealing with integers:

number.to_s.reverse.scan(/\d{1,3}/).join(",").reverse

number            #=> 12345678
.to_s             #=> "12345678"
.reverse          #=> "87654321"
.scan(/\d{1,3}/)  #=> ["876","543","21"]
.join(",")        #=> "876,543,21"
.reverse          #=> "12,345,678"

Works great for integers. Of course, this particular example will separate the number by commas, but switching to spaces or any other separator is as simple as replacing the parameter in the join method.

Jeff Klein
  • 312
  • 3
  • 8
  • 1
    ```formatted_n = n.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse``` faster than this solution ;) – Matrix Oct 30 '14 at 15:44
  • 3
    @Matrix which is probably why that is the top reply to this question :) I was just adding my take on the problem. I love Ruby because there's always a hundred different ways to do things! – Jeff Klein Nov 11 '14 at 20:20
  • 1
    you seem to take my comment as an attack, it does not have, I just read the various solutions, because the "top" is not always the best in fact. So I did a benchmark test and I give my conclusion that want to hear it. Nothing more ;) – Matrix Nov 11 '14 at 22:42
11

The official document suggests three different ways:

1) Using lookbehind and lookahead (Requires oniguruma)

12500.to_s.gsub(/(?<=\d)(?=(?:\d{3})+\z)/, ' ')
# => "12 500"

2) Using only lookahead. Identical to steenslag's answer.

3) Using neither lookahead nor lookbehind

s = 12500.to_s
nil while s.sub!(/(.*\d)(\d{3})/, '\1 \2')
s # => "12 500"
sawa
  • 165,429
  • 45
  • 277
  • 381
  • #3 appears to rely on greediness. If so, I'd think it wouldn't matter if `gsub!` or `sub!` were used. `nil` could be replaced with any literal. Correct? Is that always equivalent to `while s.gsub!(/(.*\d)(\d{3})/, '\1 \2'); end` (which I think reads a bit better)? – Cary Swoveland Nov 01 '14 at 05:07
  • @CarySwoveland Yes. `gsub!` was unnecessary. I edited. – sawa Nov 01 '14 at 07:58
6

very simple:

number_with_delimiter(12500, delimiter: " ")

see: http://apidock.com/rails/ActionView/Helpers/NumberHelper/number_with_delimiter

nilid
  • 372
  • 2
  • 12
4

Another way:

12500.to_s.reverse().split(//).inject() {|x,i| (x.gsub(/ /,"").length % 3 == 0 ) ? x + " " + i : x + i}.reverse()

You can always Open the Fixnum class and add this for convenience:

module FormatNums
  def spaceify
    self.to_s.reverse().split(//).inject() {|x,i| (x.gsub(/ /,"").length % 3 == 0 ) ? x + " " + i : x + i}.reverse()
  end
end

class Fixnum
  include FormatNums
end

12500.spaceify # => "12 500"
NullRef
  • 3,713
  • 1
  • 20
  • 18
3

So, this is pretty crazy and hackish, but it gets the job done...

12500.to_s.split("").reverse.each_slice(3).map {|y| y.join("").reverse}.reverse.join(" ")
 => "12 500" 

.to_s: convert to string
.split(""): split into separate digits
.reverse: reverse order
.each_slice(3): peel of each three digits (working from back end due to reverse)
.map {|y| y.join("").reverse}: map into an array for each three digits - join back together with no delimiter and reverse order back to original
.reverse: reverse order of mapped array
.join(" "): join mapped array back together with space delimiter
Yardboy
  • 2,777
  • 1
  • 23
  • 29
2

All but one of the answers use n.to_s. @MrMorphe's does not, but he creates an array to be joined. Here's a way that uses neither Fixnum#to_s nor Array#join.

def separate(n,c=' ')
  m = n
  str = ''
  loop do
    m,r = m.divmod(1000)
    return str.insert(0,"#{r}") if m.zero?
    str.insert(0,"#{c}#{"%03d" % r}")
  end
end

separate(1)       #=>         "1"
separate(12)      #=>        "12"
separate(123)     #=>       "123"
separate(1234)    #=>     "1 234"
separate(12045)   #=>    "12 045"
separate(123456)  #=>   "123 456"
separate(1234000) #=> "1 234 000"

Hmmm. Is that column on the right tipping?

Another way that uses to_s but not join:

def separate(n, c=' ')
  str = n.to_s
  sz = str.size
  (3...sz).step(3) { |i| str.insert(sz-i, c) }
  str
end
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
1

This is old but the fastest and most elegant way I could find to do this is:

def r_delim(s, e)                                                               
  (a = e%1000) > 0 ? r_delim(s, e/1000) : return; s << a                        
end

r_delim([], 1234567).join(',')

I'll try and add benchmarks at some point.

Mr Morphe
  • 664
  • 1
  • 7
  • 15
1

Another way: Here "delimiter is ' '(space), you can specify ',' for money conversion."

number.to_s.reverse.gsub(%r{([0-9]{3}(?=([0-9])))}, "\\1#{delimiter}").reverse
Unihedron
  • 10,902
  • 13
  • 62
  • 72
Pavan
  • 81
  • 3
  • Hey, welcome to [so]! You're welcome, and congratulations on your first upvote (from me :D) By the way, take a [tour], and you can chat in a relevant [chat room](http://chat.stackoverflow.com/) once you have sufficient [reputation](http://stackoverflow.com/help/whats-reputation). – Unihedron Nov 01 '14 at 13:08
0

I just stumbled on this thread while looking for a way to format a value as US currency. I took a slightly different approach to the regex solutions proposed:

amt = 1234567890.12
f_amt = format("$%.2f",amt)
i = f_amt.index(".")
while i > 4
  f_amt[i-3]=","+f_amt[i-3]
  i = f_amt.index(",")
end

f_amt
=> "$1,234,567,890.12"

This could be parameterized for formatting other currencies.

user4240162
  • 13
  • 1
  • 6
-2

I'm aware this is an old question but.

why not just use a substring substitution.

in pseudo code....

String numberAsString = convertNumberToString(123456);
int numLength = V.length;//determine length of string

String separatedWithSpaces = null;

for(int i=1; i<=numlength; i++){//loop over the number
separatedWithSpaces += numberAsString.getCharacterAtPosition(i);
    if(i.mod(3)){//test to see if i when devided by 3 in an integer modulo,
    separatedWithSpaces += " ";

    }//end if

}//end loop

I know it isn't in any particular languange, but hopefully you get the idea.

David

DaveM
  • 734
  • 4
  • 11
  • 35