Subject: | Math::BigInt->new() wrongly converts large floats |

MIME-Version: | 1.0 |

X-Mailer: | MIME-tools 5.504 (Entity 5.504) |

Content-Disposition: | inline |

X-RT-Interface: | Web |

Message-ID: | <rt-4.0.18-10831-1394897983-212.0-0-0 [...] rt.cpan.org> |

Content-Type: | text/plain; charset="utf-8" |

Content-Transfer-Encoding: | binary |

X-RT-Original-Encoding: | utf-8 |

X-RT-Encrypt: | 0 |

X-RT-Sign: | 0 |

Content-Length: | 1327 |

A conversion of large integral floating-point numbers into BigInts can lose
significant digits. Test case: 504403158265495552.0 == 7 * 2**56, which is
exactly representable as a float/double.
$ perl -MMath::BigInt -e 'print Math::BigInt->new(504403158265495552.0)->bstr(), "\n";'
504403158265496000
A test case that reqires more than 64 integer bits is 7 * 2**66 = 516508834063867445248.0.
The cause is that the _split function in Math::BigInt treats its argument as a string and hereby triggers Perl's automatic conversion which does not output enough digits for large integers. A fix is to explicitly convert via sprintf("%f",..), which retains all digits before the decimal point. Doing this for non-floats is superfluous and possibly harmful, so we test the accuracy of a roundtrip with the automatic conversion first:
@@ -3048,6 +3048,8 @@
# invalid input.
my $x = shift;
+ $x= sprintf("%f", $x) unless "$x" == $x; # ensure enough digits from float
+
# strip white space at front, also extraneous leading zeros
$x =~ s/^\s*([-]?)0*([0-9])/$1$2/g; # will not strip ' .2'
$x =~ s/^\s+//; # but this will
Line numbers refer to version 1.9991, which my distribution ships but CPAN does not yet (?!).
This issue was first reported to the perlbug list, as [perl #121136].