How can I sprintf a big number in Perl?
Asked Answered
G

5

5

On a Windows 32-bit platform I have to read some numbers that, this was unexpected, can have values as big as 99,999,999,999, but no more. Trying to sprintf("%011d", $myNum) them outputs an overflow: -2147483648.

I cannot use the BigInt module because in this case I should deeply change the code. I cannot manage the format as string, sprintf("%011s", $numero), because the minus sign is incorrectly handled.

How can I manage this? Could pack/unpack be of some help?

Gur answered 6/5, 2009 at 7:43 Comment(2)
Is sprintf necessary? Can you output it as a string with manual formatting?Ichinomiya
Absolutely. I must fill a fixed-size field (legacy code...) and I must address even negative numbersGur
A
11

Try formatting it as a float with no fraction part:

$ perl -v
This is perl, v5.6.1 built for sun4-solaris
...

$ perl -e 'printf "%011d\n", 99999999999'
-0000000001

$ perl -e 'printf "%011.0f\n", 99999999999'
99999999999
Astraphobia answered 6/5, 2009 at 11:18 Comment(1)
Bingooooooooo! This is THE answer. Great.Gur
M
1

Yes, one of Perl's numeric blind spots is formatting; Perl automatically handles representing numbers as integers or floats pretty well, but then coerces them into one or the other when the printf numeric formats are used, even when that isn't appropriate. And printf doesn't really handle BigInts at all (except by treating them as strings and converting that to a number, with loss of precision).

Using %s instead of %d with any number you aren't sure will be in an appropriate range is a good workaround, except as you note for negative numbers. To handle those, you are going to have to write some Perl code.

Morbihan answered 6/5, 2009 at 8:48 Comment(1)
So in your opinion it is not a lack in my knowledge of perl functions? I interpret your answer as: there is a specific issue in formatting big numers; you should fix the formatting issue writing your own routine. If so, this is perfectly acceptable walkthru, of course. My doubt was about overlooking some obvious soultion.Gur
D
1

Floats can work, up to a point.

perl -e "printf qq{%.0f\n}, 999999999999999"
999999999999999

But only up to a point

perl -e "printf qq{%.0f\n}, 9999999999999999999999999999999999999999999999"
9999999999999998663747590131240811450955988992

Bignum doesn't help here.

perl -e "use bignum ; printf qq{%.0f\n}, 9999999999999999999999999999999999999999999999"
9999999999999999931398190359470212947659194368    

The problem is printf. (Do you really need printf?) Could print work?

perl -e "use bignum;print 9999999999999999999999999999999999999999999999"
9999999999999999999999999999999999999999999999

Having said all of that, the nice thing about perl is it's always an option to roll your own.

e.g.

my $in = ...;
my $out = "";
while($in){
  my $chunk=$in & 0xf;
  $in >>= 4;
  $out = sprintf("%x",$chunk).$out;
}
print "0x$out\n";
Detta answered 23/10, 2019 at 9:52 Comment(0)
B
0

I'm no Perl expert, and maybe I'm missing some sort of automatic handling of bignums here, but isn't this simply a case of integer overflow? A 32-bit integer can't hold numbers that are as big as 99,999,999,999.

Anyway, I get the same result with Perl v5.8.8 on my 32-bit Linux machine, and it seems that printf with "%d" doesn't handle larger numbers.

Browne answered 6/5, 2009 at 7:51 Comment(3)
I'd suspect Perl automatically switches to Int64 if required, but no Perl expert either.Jinn
No, on a perl with 32-bit integers, operations that exceed the range of an integer switch to floating-point (usually a 64-bit double, which can go up to 2^53 while still keeping integer precision, although an 80- or 128-bit long double is also possible).Buchmanism
bigint is a library that handles numbers larger than fit into a 4 byte/8 byte integer. Arbitrarily large in fact. The only limit in size is available memory and CPU time. The problem is printf.Detta
P
0

I think your copy of Perl must be broken, this is from CygWin's version (5.10):

pax$ perl -e 'printf("%011d\n", 99999999999);'
99999999999

pax$ perl -v

This is perl, v5.10.0 built for cygwin-thread-multi-64int
(with 6 registered patches, see perl -V for more detail)

Copyright 1987-2007, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

What version are you running (output of perl -v)?

You may have to get a 64-bit enabled version of Perl [and possibly a new 64-bit production machine] (note the "cygwin-thread-multi-64int" in my output). That will at least avoid the need for changing the code.

I'm stating this on the basis that you don't want to change the code greatly (i.e., you fear breaking things). The solution of new hardware, whilst a little expensive, will almost certainly not require you to change the software at all. It depends on your priorities.

Another possibility is that Perl itself may be storing the number correctly but just displaying it wrong due to a printf() foible. In that case, you may want to try:

$million = 1000000;
$bignum = 99999999999;
$firstbit = int($bignum / $million);
$secondbit = $bignum - $firstbit * million;
printf ("%d%06d\n",$firstbit,$secondbit);

Put that in a function and call the function to return a string, such as:

sub big_honkin_number($) {
    $million = 1_000_000;
    $bignum = shift;
    $firstbit = int($bignum / $million);
    $secondbit = $bignum - $firstbit * $million;
    return sprintf("%d%06d\n", $firstbit, $secondbit);
}
printf ("%s", big_honkin_number (99_999_999_999));

Note that I tested this but on the 64-bit platform - you'll need to do your own test on 32-bit but you can use whatever scaling factor you want (including more than two segments if need be).

Update: That big_honkin_number() trick works fine on a 32-bit Perl so it looks like it is just the printf() functions that are stuffing you up:

pax@pax-desktop:~$ perl -v

This is perl, v5.8.8 built for i486-linux-gnu-thread-multi

Copyright 1987-2006, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

pax@pax-desktop:~$ perl qq.pl
99999999999
Picofarad answered 6/5, 2009 at 8:30 Comment(6)
You are using 64bit Perl, and Daniel said that he uses 32bit.Malady
In most cases, you can build a 64bitint perl even without a 64 bit processor, so hardware is likely not needed.Morbihan
The version i am using: This is perl, v5.8.8 built for MSWin32-x86-multi-thread (with 25 registered patches, see perl -V for more detail).Gur
Anyway, the one-liner produces -0000000001.Gur
Download the latest CygWin and give that a try. It may handle 64bit numbers on a 32-bit CPU as @Morbihan suggests, then your problems are over. No new machine, no code reworks.Picofarad
$ perl -e 'printf("%011d\n", 99999999999);' -0000000001 $ perl -v This is perl, v5.10.0 built for i486-linux-gnu-thread-multiLeverhulme

© 2022 - 2024 — McMap. All rights reserved.