http://qs321.pair.com?node_id=310534

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

Hello guys!

I want to divide a number by 1000 and leave only one digit after the period even if the number divides with no remainder, for example:
$xx = 12000/1000; # wanted result is 12.0 $yy = 12678/1000; # wanted result is 12.7 - rounded up
How can I achive that (not by playing with printf and formats - %.01f).

Replies are listed 'Best First'.
Re: Dividing and format
by kilinrax (Deacon) on Nov 27, 2003 at 14:01 UTC

    printf / sprintf is the obvious way, and given they are builtin functions, I see no reason not to use them.

    Want to give us a slightly fuller description of the problem and reasons for the restriction before this gets branded as homework? ;)

      I don't need to display the result, I first want to store the result, do some calculations with it and than display another result.

        Normally, intermediate calculations are done without rounding, to gain maximum accuracy - you round at the end for display purposes. If for some reason you actually need to round the number for intermediate calculations, then you use sprintf.

        --
        3dan

Re: Dividing and format
by Abigail-II (Bishop) on Nov 27, 2003 at 14:14 UTC
    What's the problem with using printf? Anyway, the following code seems to work (although it doesn't do 'Bankers rounding'):
    #!/usr/bin/perl use strict; use warnings; while (<DATA>) { chomp; my $o = $_; next if /\D/ || /^$/; substr $_ => -0, 0, 0 x (4 - length); $_ ++ if 50 <= substr $_ => -2, 2, ""; substr $_ => -1, 0, "."; print "$o / 1000 = $_\n"; } __DATA__ 12000 12678 5 49 50 499 500 501 12000 / 1000 = 12.0 12678 / 1000 = 12.7 5 / 1000 = 0.0 49 / 1000 = 0.0 50 / 1000 = 0.1 499 / 1000 = 0.5 500 / 1000 = 0.5 501 / 1000 = 0.5

    Abigail

Re: Dividing and format
by l3nz (Friar) on Nov 27, 2003 at 15:17 UTC
    I frankly don't understand why you should have an internal representation of the number with the decimal zero (either you want to print it or you want to compute with it, but maybe I'm wrong). Anyway the right way to go is using sprintf() to generate a correctly truncated string and then rely on Perl's ability to cast numbers in the most appropriate ways.

    Here is an example:

    use strict; while ( <DATA> ) { my $val = sprintf( "%.01f", $_/1000 ); # do maths with the truncated result.... my $val2 = $val * 2; print "$val - $val2\n"; } __DATA__ 100 1000 2314 171 123456789
    The result is
    0.1 - 0.2
    1.0 - 2
    2.3 - 4.6
    0.2 - 0.4
    123456.8 - 246913.6
    
    Note how the .0 is lost if you do any maths on 1.0 unless you format it again before printing - the first column is made of strings, the second of floats.
Re: Dividing and format
by zengargoyle (Deacon) on Nov 27, 2003 at 14:24 UTC
    $ cat test.in 12000 12678 5 49 50 499 500 501 ^D $ perl -lpe '$_=int(($_+50)/100)/10;$_.=".0"unless/\./' <test.in 12.0 12.7 0.0 0.0 0.1 0.5 0.5 0.5
Re: Dividing and format
by tadman (Prior) on Nov 27, 2003 at 16:38 UTC
    You can do it with straight math, too:
    sub round_to { my ($value, $places) = @_; my $exp = 10 ** $places; return int(($value * $exp * 10 + 5) / 10) / $exp; } print round_to(12678/1000, 1),$/;

      Perhaps bizarrely, tadman's code doesn't work with all numbers. I think it's internal rounding errors on Perl's part rather than a fault with the code (though I could be wrong :). I've broken down the code to analyse what's going on, viz:

      $number = 12650 / 10000; $dp = 2; print "Actual: $number\n"; my $exp = 10 ** $dp; $val1 = $number * $exp; $val2 = $val1 + 0.5; $val3 = int($val2); $val4 = $val3 / $exp; print "val1 = value x exp = $val1\n"; print "val2 = val1 + 0.5 = $val2\n"; print "val3 = int(val2) = $val3\n"; print "val4 = val3 / exp = $val4\n";

      The output:

      Actual: 1.265 val1 = value x exp = 126.5 val2 = val1 + 0.5 = 127 val3 = int(val2) = 126 val4 = val3 / exp = 1.26

      Verified on Win32 with ActivePerl 5.8.1 b807, and in Linux with Perl 5.8.0.

      Replace the first line with "$number = 1.265;", however, and you get the correct result. Weird.

        It's probably storing "127" as 126.9999999999999 which rounds down with the int function. Aren't floating point numbers fun?

        The documentation for int suggests that the POSIX::floor function is a better selection for this kind of work.

        Ok Goron I brought the int function up sooner to get rid of the floating point asp. And to remove decimels from the addition portion. Seen in this code based of yours.

        Code

        use strict; my $number = 12650 / 10000; #my $number = 1.265; my $dp = 2; print "Actual: $number\n"; my $exp = 10 ** ($dp+1); my $val1 = int($number * $exp); my $val2 = $val1 + 5; my $val4 = $val2 / $exp; print "val1 = value x exp = $val1\n"; print "val2 = val1 + 5 = $val2\n"; #print "val3 = int(val2) = $val3\n"; print "val4 = val2 / exp = $val4\n";

        Output

        Actual: 1.265 val1 = value x exp = 1265 val2 = val1 + 5 = 1270 val4 = va2 / exp = 1.27

        Ok I messed it up if you use 1200 you get 12.05 so I think you need to do it in steps throwing away un needed decimals. Oh well need to catch a train will play with it tonight because it is not Internet Explorer and I need something that is not Internet Explorer.

        "No matter where you go, there you are." BB
      hotshot mentioned that he wanted 12000/1000 to print as "12.0". Your pure-math method results in output of "12", and thus doesn't meet the OP's specifications.

      It would seem that, given he wishes not to use printf or formats, his best bet is the sprintf tool.


      Dave


      "If I had my life to live over again, I'd be a plumber." -- Albert Einstein
        Eventually you're going to have to use something printf-like function such as sprintf anyway. I was just proposing something that kept the numbers as numbers instead of stringifying them. It is numerically 12.0, even if print decides to show it as 12 without the decimal.

        That being said, any time you're dealing with floating point numbers it's pretty much advised to use some kind of formatting method. Some numbers might be assigned a value of 12.0 but end up displaying as 11.9999999999999999981 instead, or even 12.00000000000000012.
Re: Dividing and format
by davido (Cardinal) on Nov 27, 2003 at 17:42 UTC
    As others have mentioned, the obvious answer is to use sprintf as follows:

    my $num = 12000/1000; my $string = sprintf "%.01f", $num; print $string, "\n";

    But for no good reason I started thinking, hmm, what if we lived in some wierd world where sprintf didn't exist, but printf did?

    One could write his own sprintf function by hand. But that's not nearly as fun as abusing Perl 5.8.0 (or later):

    use strict; use warnings; require 5.8.0; my $num = 12; print format_str( "%.01f", $num ), "\n"; sub format_str { my $string; open STROUT, ">", \$string or die "You fool: $!\n"; printf STROUT shift, @_; close STROUT or die "trying: $!\n"; return $string; }

    This method doesn't meet the OP's spec, because it uses printf. But I couldn't resist an opportunity to write to an in-memory file (held in a scalar, $string) and then turn around and use it as a plain old scalar later.

    I dare you to turn that one in to the teacher. ;)

    Update: Added 'require 5.8.0;' to my snippet to protect people who might otherwise ignore my suggestion that this is only a Perl 5.8.0 or later solution.


    Dave


    "If I had my life to live over again, I'd be a plumber." -- Albert Einstein
Re: Dividing and format with regular expression the perl way
by Roger (Parson) on Nov 27, 2003 at 20:15 UTC
    Yes, you can certainly do this without using printf or sprintf. Number can be treated as string in perl. I will provide a simple regular expression solution since nobody has thought about it yet. ;-)

    #!/usr/local/bin/perl -w use strict; my $xx = 12000/1000; # wanted result is 12.0 my $yy = 12678/1000; # wanted result is 12.7 - rounded up print "\$xx = ", rounding_with_regex($xx), "\n"; print "\$yy = ", rounding_with_regex($yy), "\n"; sub rounding_with_regex { my $num = shift; $num += 0.05; # want to round up (as number) $num =~ s/\.(\d).*/.$1/; # perform the rounding (as string) return $num; }
    And the output is exactly as required -
    $xx = 12.0 $yy = 12.7
      $num =~ s/\.(\d).*/.$1/; # perform the rounding (as string)

      Oi Vay! Why (oh why) do you have that trailing dot-star in your regex? I see this with both neophyte and experienced RE users and I have yet figure out what misconception leads to this practice. Any ideas?

        Well, just drop trailing dot-star in the regex and see the result. ;-)

        I want to get rid of anything after the first decimal point, the tailing (.*) let me do that. Of course I can also rewrite the regular expression as -
        $num =~ s/\.(\d)\d+/.$1/; # perform the rounding (as string)
        But that requires 3 characters, and I am just too lazy. Besides there is an assumption that whatever passed in is a floating point.

        In this case, it removes anything after the first decimal. Perhaps you are thinking of needless .* at the end of a m//?