Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

sprintf zero-fill issue

by mlong (Sexton)
on Jun 27, 2007 at 16:20 UTC ( #623662=perlquestion: print w/replies, xml ) Need Help??
mlong has asked for the wisdom of the Perl Monks concerning the following question:

So, I am back doing some perl again for the first time in a long time. Anyhow, I ran into a problem while trying to zero fill a number. I want to ensure that the number is 14 characters long with leading zeros to fill in any missing characters. I then wrote the following script to demonstrate the issue I'm having:
#!/usr/bin/perl -w use strict; my $var1 = "002130013809"; my $var2 = "002150004800"; my $var1alt = sprintf("%014d", $var1); my $var2alt = sprintf("%014d", $var2); print "$var1 => $var1alt\n"; print "$var2 => $var2alt\n";
I expected to get this output:
002130013809 => 00002130013809
002150004800 => 00002150004800

but instead I get this:
002130013809 => 00002130013809
002150004800 => -0002144962496

The first number gets it right, but the second is way off and places a minus sign in front. What gives?

Thanks for your help.


Replies are listed 'Best First'.
Re: sprintf zero-fill issue
by arikalish (Acolyte) on Jun 27, 2007 at 16:36 UTC
    You've hit an overflow. 2150004800 is bigger than 2^32/2. Due to the way computers handle positive/negative numbers, you end up with a negative number.

    Try this and you should get a positive result:

    my $var1alt = sprintf("%014d", $var1);
    my $var2alt = sprintf("%014u", $var2);

    The 'u' tells Perl to treat the variable as an unsigned integer.

    See for a detailed description
      Yep. That was it. I'm not sure how I missed that one.


        Thanks to you both, this inadvertently helped correct an issue that I've had myself and had forgotten about until I saw this.
Re: sprintf zero-fill issue
by toolic (Bishop) on Jun 27, 2007 at 16:44 UTC
    I tried using "long unsigned", and it works for me:
    my $var1alt = sprintf("%014lu", $var1); my $var2alt = sprintf("%014lu", $var2);
Re: sprintf zero-fill issue
by Anonymous Monk on Jun 27, 2007 at 17:35 UTC
    In addition to unsigned, you could also indicate them as a string, assuming your inputs are strings (as they are in the example you provided).
    printf("%014s", $var1); printf("%014s", $var2);
    Of course, I can't say for sure that wouldn't cause some other unforeseen issue. just my $0.02
Re: sprintf zero-fill issue
by ysth (Canon) on Jun 28, 2007 at 05:21 UTC
    Unfortunately, using printf numeric formats doesn't interact well with perl's habit of storing things in whatever form it thinks best, be it floating point, integer, or unsigned integer.

    Any of the numeric formats will result in a C-level cast into the type specified by the format (though perl will use the correct length modifiers, never casting to int or float when it's using long or double). So %e, %f, and %g cast to a floating point number, potentially losing precision if perl is using 64 bit integer types, while %d, %u, %o, %x can lose precision or range or cast an unsigned to signed or vice versa.

    A simple example:

    $ perl $x = 2**65; printf("%d\n", $x); __END__ -1
    Here, $x is a floating point value. Perl knows you want an integer type, so it tries to convert it, but it's out of range, so UV_MAX is used instead (likely 2**32-1 or 2**64-1) but that is cast to a signed integer type, giving -1.

    You're best off sticking to an %s (or %_) format whenever possible.

Re: sprintf zero-fill issue
by snopal (Pilgrim) on Jun 27, 2007 at 22:17 UTC

    I like:

    my $var1alt = substr "00000000000000".$var1, -14; my $var2alt = substr "00000000000000".$var2, -14;
Re: sprintf zero-fill issue
by Anonymous Monk on Jun 27, 2007 at 20:36 UTC
    You could do something string based ( and perhaps very silly ):
    #!/usr/bin/perl -w use strict; my $var1 = "002130013809"; my $var2 = "002150004800"; my $goodlength = '14'; if ( length($var1) < $goodlength ){ $var1 = ('0' x ($goodlength - length($var1))).$var1; }; if ( length($var2) < $goodlength ){ $var2 = ('0' x ($goodlength - length($var2))).$var2; }; print "var1: $var1\n"; print "var2: $var2\n"; __END__
    Which gives:
    var1: 00002130013809 var2: 00002150004800
Re: sprintf zero-fill issue
by Moron (Curate) on Jun 28, 2007 at 09:24 UTC
    I tend to do this:
    sub Lzero { my ( $input, $padToLen ) = @_; substr( ( '0' x $padToLen ) . $input , -$padToLen ); }

    ^M Free your mind!

Re: sprintf zero-fill issue
by girarde (Hermit) on Jun 28, 2007 at 13:31 UTC
    I usually go with:

    my $delta = $desired_length - length $number; while ($delta ne 0) { $number = '0' . $number; --$delta; }
    which may not be the most efficient, but I never have to look up sprintf, and neither will anyone who reads the script. This is a consideration for me, since almost nobody else at my shop knows perl, and a lot them don't know sprintf from Unix or C++ either.
      I do it similar to you
      my $delta = $maxSize - length( $value ); $value = ( '0'x$delta ).$value if ( $delta > 0 );
      Having said that, I really like the construction Moron described, and plan on trying it. (I'm also the only Perl practioner in my shop. Sadly.)

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://623662]
Approved by Corion
Front-paged by Corion
and the pool shimmers...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (3)
As of 2018-05-20 16:43 GMT
Find Nodes?
    Voting Booth?