[lug] Perl and large numbers on (K)ubuntu

Anthony Foiani tkil at scrye.com
Tue Sep 27 03:45:53 MDT 2011


Gary Hodges <Gary.Hodges at noaa.gov> writes:

> Thank you for the reply.  Here is a short program that shows what I am 
> describing.
> ----------
> #!/usr/bin/perl
> use Date::Manip;
> my $secs = 2147483647;  # Increment by one to see the error
> my $time = &DateCalc("Jan 1, 1900  00:00:00","+ $secs");
> print "$secs $time\n";  # $time is in yyyymmddhh:mn:ss format
>
> # My output (second line is w/secs incremented by one):
> # 2147483647 1968012003:14:07
> # 2147483648 1831121320:45:56

I can't guarantee this will work, but from my experiments, it looks
like it's an issue with representing the delta as a string.

Please take a look at:

   http://scrye.com/~tkil/perl/date-time-bigint.plx

On Fedora 15 x86_32, I get this output:

| (2<<31)-1, raw:      1968-01-20T03:14:07Z
| 2<<31, raw:          1968-01-20T03:14:08Z
| (2<<31)+1, raw:      1968-01-20T03:14:09Z

Using 'raw deltas' works fine.  This is where I construct the delta
with a simple string "+0s", then directly set the seconds via a
mutator.

| (2<<32)-1, raw:      2036-02-07T06:28:15Z
| 2<<32, raw:          2036-02-07T06:28:16Z
| (2<<32)+1, raw:      2036-02-07T06:28:17Z

This is a pleasant surprise.  I'm not sure exactly how/why this works,
unless a double has enough digits of precision to handle this.  (Which
it does; the 53-bit mantissa in a 64-bit double is more than enough to
cover 32 bits of an int32_t / uint32_t.)

| (2<<31)-1, str:      1968-01-20T03:14:07Z
| 2<<31, str:          1831-12-13T20:45:52Z
| (2<<31)+1, str:      1831-12-13T20:45:53Z

Using the direct string "+2147483647s" doesn't work.  I don't have the
brain at the moment to dig into the module; it might be worth filing
as a bug report, honestly.

| (2<<31)-1, str+bi:   1968-01-20T03:14:07Z
| 2<<31, str+bi:       1831-12-13T20:45:52Z
| (2<<31)+1, str+bi:   1831-12-13T20:45:53Z

It doesn't get any better if I add a single "use bigint" at the top
of the program...

| (2<<31)-1, str+bi2:  1968-01-20T03:14:07Z
| 2<<31, str+bi2:      1831-12-13T20:45:52Z
| (2<<31)+1, str+bi2:  1831-12-13T20:45:53Z

Nor if I try harder, injecting "use bigint" into a couple of packages
within the Date::Manip tree.

Anyway.  Hopefully you can use this information to build a work-around
into your code.

You might also consider migrating to a 64-bit platform, if that's an
option; I'm starting to see comments on, e.g., linux-kernel, to the
extent of "oh, that slipped through because none of the devs build on
32-bit anymore...".

To wit, on x86_64, without any code mods, I get:

| (2<<31)-1, raw:      1968-01-20T03:14:07Z
| 2<<31, raw:          1968-01-20T03:14:08Z
| (2<<31)+1, raw:      1968-01-20T03:14:09Z
| (2<<32)-1, raw:      2036-02-07T06:28:15Z
| 2<<32, raw:          2036-02-07T06:28:16Z
| (2<<32)+1, raw:      2036-02-07T06:28:17Z
| (2<<31)-1, str:      1968-01-20T03:14:07Z
| 2<<31, str:          1968-01-20T03:14:08Z
| (2<<31)+1, str:      1968-01-20T03:14:09Z
| (2<<31)-1, str+bi:   1968-01-20T03:14:07Z
| 2<<31, str+bi:       1968-01-20T03:14:08Z
| (2<<31)+1, str+bi:   1968-01-20T03:14:09Z
| (2<<31)-1, str+bi2:  1968-01-20T03:14:07Z
| 2<<31, str+bi2:      1968-01-20T03:14:08Z
| (2<<31)+1, str+bi2:  1968-01-20T03:14:09Z

HTH,
t.

p.s. Here's the source, in case the URL goes walkabout:

| #!/usr/bin/perl
| 
| use strict;
| use warnings;
| 
| use Date::Manip;
| 
| sub fmt
| {
|     my ( $date ) = @_;
|     return $date->printf( "%O%Z" );
| }
| 
| my $start_str = "1900-01-01T00:00:00Z";
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+0s" );
|     $delta->set( s => 2147483647 );
|     my $time = $start->calc( $delta );
|     print "(2<<31)-1, raw:      ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+0s" );
|     $delta->set( s => 2147483648 );
|     my $time = $start->calc( $delta );
|     print "2<<31, raw:          ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+0s" );
|     $delta->set( s => 2147483649 );
|     my $time = $start->calc( $delta );
|     print "(2<<31)+1, raw:      ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+0s" );
|     $delta->set( s => 4294967295 );
|     my $time = $start->calc( $delta );
|     print "(2<<32)-1, raw:      ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+0s" );
|     $delta->set( s => 4294967296 );
|     my $time = $start->calc( $delta );
|     print "2<<32, raw:          ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+0s" );
|     $delta->set( s => 4294967297 );
|     my $time = $start->calc( $delta );
|     print "(2<<32)+1, raw:      ", fmt( $time ), "\n";
| }
| 
| # ----------------------------------------------------------------------
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483647s" );
|     my $time = $start->calc( $delta );
|     print "(2<<31)-1, str:      ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483648s" );
|     my $time = $start->calc( $delta );
|     print "2<<31, str:          ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483649s" );
|     my $time = $start->calc( $delta );
|     print "(2<<31)+1, str:      ", fmt( $time ), "\n";
| }
| 
| # ----------------------------------------------------------------------
| 
| use bigint;
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483647s" );
|     my $time = $start->calc( $delta );
|     print "(2<<31)-1, str+bi:   ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483648s" );
|     my $time = $start->calc( $delta );
|     print "2<<31, str+bi:       ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483649s" );
|     my $time = $start->calc( $delta );
|     print "(2<<31)+1, str+bi:   ", fmt( $time ), "\n";
| }
| 
| # ----------------------------------------------------------------------
| 
| package Date::Manip::Delta;
| use bigint;
| package Date::Manip::Calc;
| use bigint;
| package main;
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483647s" );
|     my $time = $start->calc( $delta );
|     print "(2<<31)-1, str+bi2:  ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483648s" );
|     my $time = $start->calc( $delta );
|     print "2<<31, str+bi2:      ", fmt( $time ), "\n";
| }
| 
| {
|     my $start = Date::Manip::Date->new( $start_str );
|     my $delta = $start->new_delta( "+2147483649s" );
|     my $time = $start->calc( $delta );
|     print "(2<<31)+1, str+bi2:  ", fmt( $time ), "\n";
| }
| 
| exit 0;




More information about the LUG mailing list