Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Formating numbers with thousand separator - Solution for web-applications

by Zzenmonk (Sexton)
on May 27, 2013 at 14:17 UTC ( [id://1035411]=CUFP: print w/replies, xml ) Need Help??

I read a lot on this topic. There are solutions for this mater. The most promising involves a module named Number::Format. I found also regexp based solutions, which gave me a headache and which I could not get to work properly. Finally there are millions of fancy code bits around for this purpose. There is also one solution to forget definitively: sprintf!

The solution I am posting here is thought for web-application. Web-application written in PERL have performance issues and unfortunately performance, meaning user perceived response times, are THE acceptance factor for your application. To solve this issue we fortunately have a wonderful Apache module named mod_perl.

Mod_perl can be tuned by loading modules into memory with the PerlModule directive. However you can not load infinitely modules in memory, since it is a scarce resource (more information on http://perl.apache.org/docs/1.0/guide/performance.html). Therefore I discarded the Number::Format module solution, which holds much more functions than what I need.

A web-application can have two layers of validation and formating. The first layer being the browser, the second being the PERL code on the server. The best is of course to validate and format your data at both layer. Therefor I was looking for a solution in JavaScript and one in PERL, which would be similar. So I realized them...

Here the JavaScript function:

<script type='text/javascript'> function formatNumber(myElement) { // JavaScript function to inser +t thousand separators var myVal = ""; // The number part var myDec = ""; // The digits pars // Splitting the value in parts using a dot as decimal separat +or var parts = myElement.value.toString().split("."); // Filtering out the trash! parts[0] = parts[0].replace(/[^0-9]/g,""); // Setting up the decimal part if ( ! parts[1] && myElement.value.indexOf(".") > 1 ) { myDec += ".00" } if ( parts[1] ) { myDec = "."+parts[1] } // Adding the thousand separator while ( parts[0].length > 3 ) { myVal = "'"+parts[0].substr(parts[0].length-3, parts[0].le +ngth )+myVal; parts[0] = parts[0].substr(0, parts[0].length-3) } myElement.value = parts[0]+myVal+myDec; } </script> <!-- in the html page --> <input name="amount" id="amount" type="text" onkeyup="formatNumber(thi +s);">

The same function in PERL

sub thousandformat { # Formats number with thousand separators my ($number) = @_; my $currnb = ""; my ($mantis, $decimals) = split(/\./, $number, 2); $mantis =~ s/[^0-9]//g; while ( length($mantis) > 3 ) { $currnb = "'".(substr($mantis, length($mantis)-3, 3 )).$currnb +; $mantis = substr($mantis, 0, length($mantis)-3); } $currnb = $mantis.$currnb; if ( $decimals ) { $currnb .= ".".$decimals; } else { $currnb .= ".00"; } return $currnb; }

Enjoy

K

The best medicine against depression is a cold beer!

Replies are listed 'Best First'.
Re: Formating numbers with thousand separator - Solution for web-applications
by Corion (Patriarch) on May 27, 2013 at 14:25 UTC

    Where in your code do you handle localization?

    For example Germany uses comma as the decimal separator and dot as the thousands separator. Especially in a web application, this is an important point to acknowledge.

    Coincidentially, the Booking.com dev blog has an article about this.

    A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Formating numbers with thousand separator - Solution for web-applications
by hdb (Monsignor) on May 27, 2013 at 18:00 UTC

    Hi K, I took your comment that this is difficult with regex as a challenge. As I do not speak any Javascript, I have no idea whether my little code can be translated, so it will most likely not fit your purpose. I found it more difficult than expected, especially when trying to add variables for the separators. So here is my piece, not to be taken as a competitor to yours:

    use strict; use warnings; sub fmthd { # number, decimal separator, thousands separator my ( $dec, $thd ) = ( $_[1] // ".", $_[2] // "," ); my ( $n, $d ) = split /\Q$dec\E/, shift; my $s = $n<0?"-":""; $n = reverse $n; return $s.reverse( $n = join $thd, $n =~ /(\d{1,3})/g ).($d?"$dec$d" +:"${dec}00"); } print fmthd( 1234567.14 ), "\n"; print fmthd( 1234567 ), "\n"; print fmthd( 12.14 ), "\n"; print fmthd( 123456.14343 ), "\n"; print fmthd( 1234567.14 ), "\n"; print fmthd( 8881234567.14 ), "\n"; print fmthd( -8881234567.14 ), "\n"; print fmthd( "-8881234567,14", ",", "." ), "\n";

    One comment though: your Perl code seems to lose the minus sign for negative numbers.

      Thx

      This snippet is anyway only thought as a base. It needs to be customized for your requirements. I found this solution nice because it is short and easy to understand (and to debug!). Accordingly you can easily add features in JavaScript or PERL without ending up with a code monster. I use the same principle for example to format the IBAN account number (a financial standard to identify an account made of 12 to 34 alphanumeric characters) for display purposes.

      As I searched the net I found a lot of solutions with regexp especially in the area of JavaScript. Things like /\B(?=(\d{3})+(?!\d))/'/g, if it may inspire you. I tried two or three of them and gave up because they did not work.

      An expert in C would also tell you, there is a solution with the printf function family. Not being an expert, I could not find a format mask allowing for any number size (in fact it takes more time to read the man page than to write this).

      Effectively this code is loosing the signs. It was designed to format amounts for a banking application. In this application the amounts are always positive. So I did not have the issue.

      K

      The best medicine against depression is a cold beer!
Re: Formating numbers with thousand separator - Solution for web-applications
by johngg (Canon) on Jun 11, 2013 at 22:32 UTC

    A couple of alternatives, just catering for positive integers, that don't use regular expressions.

    $ perl -Mstrict -Mwarnings -E ' say join q{,}, reverse map scalar reverse, unpack q{(a3)*}, scalar reverse for qw{ 1 12 123 1234 12345 123456 1234567 12345678 123456789 1234567890 };' 1 12 123 1,234 12,345 123,456 1,234,567 12,345,678 123,456,789 1,234,567,890 $
    $ perl -Mstrict -Mwarnings -E ' say sub { my $v = shift; return $v if length $v < 4; my $o = 0; substr $v, $_, 0, q{,} for reverse map { $o -= 3 } 1 .. int( ( length( $v ) - 1 ) / 3 ); return $v; }->( $_ ) for qw{ 1 12 123 1234 12345 123456 1234567 12345678 123456789 1234567890 };' 1 12 123 1,234 12,345 123,456 1,234,567 12,345,678 123,456,789 1,234,567,890 $

    There are several solutions in this thread.

    Cheers,

    JohnGG

Re: Formating numbers with thousand separator - Solution for web-applications
by grantm (Parson) on May 29, 2013 at 22:03 UTC
Re: Formating numbers with thousand separator - Solution for web-applications
by Jenda (Abbot) on May 31, 2013 at 13:45 UTC

    If you are so concerned about performance I guess you do know exactly how much memory does Number::Format add, right? And you know how does your apparently inefficient code fare against regexp based solutions. Right? You've benchmarked, right? And you found out formatting numbers made a difference, right?

    If your rates are so high I want to believe you did not waste time. I don't though.

    Jenda
    Enoch was right!
    Enjoy the last years of Rome.

Re: Formating numbers with thousand separator - Solution for web-applications
by wee (Scribe) on Jun 18, 2013 at 21:35 UTC
    That's a lot of code for what should be fairly straightforward:
    my $sep = ','; my $number = '12345678.27'; print MakeThousands($number), "\n"; sub MakeThousands { local $_ = reverse(shift()); s/(\d{3})(?=\d)(?!\d*\.)/$1$sep/go; return scalar(reverse()); }
    I know in Perl there's always a balance to be struck between making code clear and keeping it concise (at least for me), but when you have three times as much code to do something fairly basic, you run the risk of adding bugs through typos, having slower execution, etc. If I just want to print numbers like "12,345,678.27", why use more code than is necessary? If you abstract out the thousands separator, this shouldn't be a sub you touch more than once. Speaking of slower code, you mentioned something about performance. Your solution is roughly twice as slow as the one I have above:
    Benchmark: timing 5000000 iterations of MakeThousands, Thousandformat. +.. MakeThousands: -1 wallclock secs ( 0.06 usr + 0.00 sys = 0.06 CPU) @ + 83333333.33/s (n=5000000) Thousandformat: 1 wallclock secs ( 0.12 usr + 0.00 sys = 0.12 CPU) +@ 41666666.67/s (n=5000000)
    If execution speed is really your #1 concern, you might take an eye toward simplification.
Re: Formating numbers with thousand separator - Solution for web-applications
by Deven (Novice) on Feb 25, 2022 at 04:06 UTC
    A more recent addition to perlfaq5:
    This regex from Benjamin Goldberg will add commas to numbers:
    s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
    It is easier to see with comments:
    s/( ^[-+]? # beginning of number. \d+? # first digits before first comma (?= # followed by, (but not included in the match) : (?>(?:\d{3})+) # some positive multiple of three digits. (?!\d) # an *exact* multiple, not x * 3 + 1 or whatever. ) | # or: \G\d{3} # after the last group, get three digits (?=\d) # but they have to have more digits after them. )/$1,/xg;

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://1035411]
Approved by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (10)
As of 2024-04-19 09:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found