La12 has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,
Here's my situation...
$foo = 1500 and I need it to print as "15.00" so I tried...
$foo =1500; printf ("%.2f", $foo);
but that gives me 1500.00 unless I first multiply it by .01 .
I would like to avoid "doing math". Also some numbers are going to be "x.xx" or "xx.x" . I hope I explained my problem clearly. Any further questions, let me know.
Thanks

Replies are listed 'Best First'.
Re: Help with decimals
by HyperZonk (Friar) on Jul 29, 2001 at 23:33 UTC
    The printf statement doesn't change the value it's printing, just the format. Thus, formatting 1500 with 2 decimal places should do exactly what you described it doing: printing 1500.00.

    I do not really understand what you mean by avoiding "doing math" ... why do you not want to use math (as far as I know, you pretty much have to do math to meaningfully and reliably generate the output you desire). Perhaps you mean that you do not want to alter the value of $foo to generate the output, and also do not want to generate a new variable just for output formatting. If so, that is simple enough:
    $foo = 1500; printf ("%.2f", $foo/100);
    This generates the desired output without affecting the value of $foo and without using another variable. Yes, you have still multiplied by 0.01, but that value is only used for the printf output and then vanishes.

    -HZ
Re: Help with decimals
by rchiav (Deacon) on Jul 29, 2001 at 23:49 UTC
    HyperZonk's way of doing it is much better, but if you didn't want to do "math" (and I'm really not sure why), you could use chop. It's much uglier, cumbersome and all around a worse method.. but in spirit of TIMTOWTDI..
    #!/usr/bin/perl -w use strict; my $foo; $foo = 1500; print myformat($foo, 2) . "\n"; sub myformat { my ($number, $places) = @_; my $decimal; $decimal = ""; for (1 .. $places) { if ($number) { $decimal .= chop $number; } else { $decimal .= 0; } } return "$number\." . reverse $decimal; }
    Update: Changed this to be a sub so it doesn't munge the original value as suggested by HyperZonk.

    I also added the reverse because I no-so-brillantly forgot that the decimals would be reversed.

    Rich

      Your plucking the digits off one end, sticking on the dot, and sticking them back on sounds like something a regex should do!

      $number .= '0' while length($number) < $places; # add trailing zeros if needed $number =~ /(.{$places})/\.$1/;
      Or a substr
      # add trailing zeros, as above. substr ($number, -$places, 0, '.');
        Acutally a regex isn't all that good of an idea. It's very slow. But substr is better than chop.. benchmarks included..

        Benchmark: timing 100000 iterations of chop, math, regex, substr... chop: 6 wallclock secs ( 4.92 usr + 0.03 sys = 4.95 CPU) @ 202 +02.02/s (n=100000) math: 2 wallclock secs ( 1.07 usr + 0.02 sys = 1.09 CPU) @ 917 +43.12/s (n=100000) regex: 25 wallclock secs (17.40 usr + 0.32 sys = 17.72 CPU) @ 56 +43.34/s (n=100000) substr: 2 wallclock secs ( 1.99 usr + 0.03 sys = 2.02 CPU) @ 495 +04.95/s (n=100000)
        Doing the math is still the best option..
(ichimunki) Re: Help with decimals
by ichimunki (Priest) on Jul 30, 2001 at 03:51 UTC
    You say that some of your numbers will already have decimals in them. If you have a number like 1500 that you want to display as 15.00, then what do you want to do with numbers like 1501.5 or 1501.55? Do you want them to print as 15.02 and 15.02 (correctly rounded)? Or do you want them to print as 1501.50 and 1501.55?

    If the former, then the printf( "%.2f", $foo/100 ) solution works fine (although I don't know if it will round correctly for display or not)... If the latter, then you really need to ask why you are storing your numbers in such a disparate fashion and consider standardizing. Otherwise you have to convert units (i.e. do math) at some point, utterly dependent on the units of the original number.
Re: Help with decimals
by La12 (Sexton) on Jul 30, 2001 at 05:59 UTC
    Sorry to bring so much confusion to this problem. Let me explain my comment as to why I wanted to avoid math.

    $file = 100+ lines of 12 field comma delimited text and numbers (text,text,num,num,num,...) there are no decimals in these numbers.

    $bar = a number from 0 thru 9 that will be given to me by the third field in one 12 field comma delimited line. This number will determine the number of decimal spaces that the other fields in that same line will have.

    $foo = numerical fields number 4 thru 8.

    I thought I could say something like:
    if $bar = x then put a "." x+1 characters in from the right of $foo.
    as opposed to saying:

    if $bar = 2 then $foo * .01 .. printf...
    if $bar = 3 then $foo * .001 printf ...
    if $bar = 4 then $foo * .0001 printf...

    So this is why I thought that it would be easier not to do math.
    I'm really sorry about not explaining myself properly.
    Hope this helps.
      First of all, let me say that there must be a better way for you to store your data. However, you might be stuck with the format, for all I know, so on with an attempted solution ...

      Okay, let's say you've split one line into fields using the appropriate module. You store those fields in @currentLine. Then you can do something like:
      foreach (@currentLine[3 .. 11]) { printf ("%.*f", $currentLine[2], $_/(10 ** $currentLine[2]) }
      The * sucks up the next variable in the list and uses it as the field width; in this case, it slurps the first appearance of $currentLine[2] in the list.

      Update: As tilly has noted, the module I mentioned above does not handle newlines embedded within records. Another module, recommended by the same esteemed personage who is on vacation does that, if it is required.

      -HZ
        You're right about the data. The file is given to me by a client. I have no control over the format.
Re: Help with decimals
by La12 (Sexton) on Jul 30, 2001 at 06:07 UTC
    one more example...

    If the number is 1368 and $bar is 3
    then the result should be 1.368

    if the number xxxxx and $bar is 2
    then the result should be xxx.xx

      Then you just want to do a..
      #!/usr/bin/perl -w use strict; my ($foo, $bar); $foo = 1500; $bar = 2; print $foo / (10 ** $bar) ."\n";
      Hope this helps..
      Rich
Re: Help with decimals
by La12 (Sexton) on Jul 30, 2001 at 07:23 UTC
    Rich,

    When I run your script I get "15" not "15.00"

    if $foo = 1500 and...
    if $bar = 4 then the result should be .1500
    if $bar = 3 then the result should be 1.500
    if $bar = 2 then the result should be 15.00
    if $bar = 1 then the result should be 150.0
    if $bar = 0 then the result should be 1500

    another example if $foo = 12653 and...
    if $bar = 4 then the result should be 1.2653
    if $bar = 3 then the result should be 12.653
    if $bar = 2 then the result should be 126.53
    if $bar = 1 then the result should be 1265.3
    if $bar = 0 then the result should be 12653

    Again, this is why I thought of it as "lets put add a dot to $foo at $bar+1 from the right (I don't know how to code that)"
    as opposed to lets multiply $foo by .01 or .001 etc.
      First make sure $bar is numeric, then...
      printf ("%.${bar}f", $foo / (10 ** $bar));
      To deconstruct:

      Take 10 to the power $bar to get the correct divisor. Then divide by it to move the decimal place over non-destructively. Then, to give the correct number of places, use printf and %. f -- note that there must be {} around bar because otherwise Perl will try to interpolate $barf, which, if you're using strict and have no variable called $barf will indeed barf. To get the string value rather than printing it out, use sprintf rather than printf.

      So, for instance, if you have $foo being 570 and $bar being 1, you get (reducing the expression, so to speak):

      1. printf ("%.${bar}f", $foo / (10 ** $bar))
      2. printf ("%.1f", 570 / (10 ** 1))
      3. printf ("%.1f", 570 / 10)
      4. printf ("%.1f", 57)
      5. "57.0"
      ... which is what you wanted I think.
      Do you want the decimals even if they aren't siginficant? 2 zeros aren't significant. if you change the number to 1555, it will print 15.55. It just drops non significant digits.
        yes I do. I understand 15 is the same as 15.00, but I need these zeros to remain in $foo's end result. The original digits in $foo must remain (i.e. $foo=1500 then result must have 1500 in it).
        I'm sorry, I did not try to use a different number in your script.
Re: Help with decimals
by Monky Python (Scribe) on Jul 30, 2001 at 17:18 UTC
    This code solves the problem without "doing math".

    #!/usr/bin/perl -w use strict; my $foo = 17432; my $bar = 3; $foo =~ s/([0-9]+)([0-9]{$bar})/$1.$2/; print $foo;

    MP

Re: Help with decimals
by La12 (Sexton) on Jul 30, 2001 at 22:01 UTC
    Thanks everyone for your input.

    I've decided to go with premchai21's suggestion which "does math", as opposed to Monky Python suggestion which is exactly what I was looking for (I can hear everyone now... 'Is he nuts?!', 'Has he gone mad?', 'First he asks for 'no math' and then he decides to figure out the exact value of pi!', 'Burn him at the stake!').

    Ah, but you see my friends, you have all taught me the error of my ways. Math did not make finding a solution more complicated. My problem was that I was not "seeing" the proper math and thus thought it would be a messy solution. In fact, because I cannot trust the intergrity of the data handed to me, math makes my inital problem easier to solve.
    My fear is that $bar may be greater than the number of digits in $foo and thus I would need to add zeros in front of the number ($bar=5 $foo=1500 correct result = .01500). This could be done with reg exp, but my new thinking (IMHO) is that math would be a cleaner solution (I hope i'm not opening a can of worms here).

    Thanks again everyone for the learning experience.