in reply to Re: How to use the int-function?
in thread How to use the int-function?

Thank you, fellow monks. While two of the above answers pointed out that int is exactly doing what its supposed to do, Rata followed the rather pragmatic approach to show how to actually use the int-function to get the expected result. Let me please try to hold awaken the interest in the practial approach, while peeking into the computer scientist material later.

In the following view on the problem, I try to round the number 1.255, and try to get the result 1.26. How would you perform this with the typical on-board means, to which undenyingly int() belongs to?

#!/usr/bin/perl my $i = 1.255; print int($i*100+.5)/100; print "\n";

Please register, that the same code will do as expected when using "1.355" as value for $i.

Replies are listed 'Best First'.
Re^3: How to use the int-function?
by kennethk (Abbot) on Jan 03, 2011 at 15:41 UTC
    If you are going to be formatting a number for output, I would suggest you use the methods designed specifically to handle that - printf and sprintf. If you are rounding/truncating a value in the context of numerical manipulation, then the approach you take very much depends on your specific needs. There is a very rich field of academic study revolving around performing computations with discrete approximations.

    #!/usr/bin/perl use strict; use warnings; my $i = 1.255; my $j = $i * 100 + 0.5; $j *= 10000000; printf "%.0f\n", $j;

    outputs

    1260000000

      kennethk, I've not yet found that printf would help me with formatting the output:

      perl -e 'printf("%.2f\n", 1.255)'

      -> 1.25

      perl -e 'printf("%.2f\n", 1.455)'

      -> 1.46

      Although, there might be some option which jumps into the gap and helps to round the number?

        Because the double precision approximation of 1.255 is slightly below 1.255 and the double precision approximation of 1.455 is slightly above 1.455. You introduce the error during the assignment, not during the manipulation. Ratazong's solution actually introduces additional errors into the process since now you have two approximated numbers, as well as additional maintenance complexity.

        Why is your application so sensitive to this change? There are ways to handle it, but this is a consequence of the real hardware you are working with. Depending on your application, you could do all your math with integers, use Math::BigFloat, use Math::BigRat, or tweak your algorithm/order of operations.

        If you're going to use printf to round then you have to be aware that it uses the Round half to even method of rounding. I've been bit by that before.

        perl -e 'foreach my $i ( 0.5, 1.5, 2.5, 3.5 ) { printf("$i -> %.0f\n", $i) }'
        0.5 -> 0 1.5 -> 2 2.5 -> 2 3.5 -> 4

        Without resorting to another module to round for you, you probably want use int() something like this (being careful to adjust the rounding value to match your circumstances).

        perl -e 'foreach my $i ( 1.155, 1.255, 1.355, 1.455 ) { print $i, " -> " , int($i * 100 + 0.5001)/100 . "\n" }'
        1.155 -> 1.16 1.255 -> 1.26 1.355 -> 1.36 1.455 -> 1.46
Re^3: How to use the int-function?
by Anonymous Monk on Jan 04, 2011 at 08:48 UTC
    Hello there, its me again, the Anonymous Monk! :-)

    Starting a new day, I felt refreshed to wrestle with the rounding problem again. To get to the beef, here is my proposal to round numbers using the int-function:

    #!/usr/bin/perl my $float = $ARGV[0] ? $ARGV[0] : 1.255; my $decimals = $ARGV[1] ? $ARGV[1] : 2; print &round( $float , $decimals ) . "\n"; sub round { my $float = shift; my $decimals = shift; my $int_leftShiftFloat = int( $float * 10**($decimals + 1) ); my $int_Round = int( ( $int_leftShiftFloat + 5 ) / 10 ); my $float_rightShiftInt = $int_Round / 10**$decimals; my $float_Result = $float_rightShiftInt; return $float_Result } sub round_ { my $float = shift; my $dec = shift; return int( ( int( $float * 10**($dec + 1) ) + 5 ) / 10 ) / 10**$dec }
    I've provided to functionally identical versions of the sub, so you may pick up which one is easier for you to read.

    In the long version of the sub, I've tried to use speaking variable names and left out any comments instead.

    To make a general comment on this solution, I've followed the suggestions provided yesterday by my fellow monks, and made the computation using integers to avoid the floating point hassle. What do you think about it?

      A couple comments:
      1. my $decimals = $ARGV[1] ? $ARGV[1] : 2; won't perform as expected if the user enters 0. You will get expected behavior if you use my $decimals = defined $ARGV[1] ? $ARGV[1] : 2;. You should also swap your $float assignment.
      2. On the same point, if you have v5.10 or greater, you can use the 'defined or' operator (//)to do the same thing: my $decimals = $ARGV[1] // 2;
      3. The & is largely unnecessary in modern Perl; your code functions just fine without it.
      4. I'd be a little gun-shy about using the same variable name in the script level variables as in the subroutine level variables - it's easy to introduce a difficult bug.
      5. You can use list assignment rather than a series of shifts if you want to save a few key strokes, replacing

        my $float = shift; my $dec = shift;

        with

          my ($float, $dec) = @_;

        This is almost entirely cosmetic in this context.

      I would also point out your algorithm will still be subject to the whims of these small deviations between string and double precision representation. Consider the default case of:

      #!/usr/bin/perl use strict; use warnings; my $number = defined $ARGV[0] ? $ARGV[0] : 1.2549999999999999; my $places = defined $ARGV[1] ? $ARGV[1] : 2; printf "%.20f\n", $number; print round( $number , $places ), "\n"; sub round { my ($float, $decimals) = @_; my $int_leftShiftFloat = int( $float * 10**($decimals + 1) ); my $int_Round = int( ( $int_leftShiftFloat + 5 ) / 10 ); my $float_rightShiftInt = $int_Round / 10**$decimals; my $float_Result = $float_rightShiftInt; return $float_Result }
      which outputs:

      1.25499999999999990000 1.26