Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Perl detect if a floating point number has been implicitly rounded?

When I use the code:

(sub {
    use strict;
    use warnings;

    print 0.49999999999999994;
})->();

Perl outputs "0.5".

And when I remove one "9" from the number:

(sub {
    use strict;
    use warnings;

    print 0.4999999999999994;
})->();

It prints 0.499999999999999.

Only when I remove another 9, it actually stores the number precisely.

I know that floating point numbers are a can of worms nobody wants to deal with, but I am curious if there is a way in Perl to "trap" this implicit conversion and die, so that I can use eval to catch this die and let the user know that the number they are trying to pass is not supported by Perl in its' native form(So the user can maybe pass a string or an object instead).

The reason why I need this is to avoid a situations like passing 0.49999999999999994 to be rounded by my function, but the number gets converted to 0.5, and in turn gets rounded to 1 instead of 0. I am not sure how to "intercept" this conversion so that my function "knows" that it did not actually get 0.5 as input, but that the user's input was intercepted.

Without knowing how to intercept this kind of conversion, I cannot trust "round" because I do not know whether it received my input as I sent it, or if that input has been modified(at compile time or runtime, not sure) before the function was called(and in turn, the function has no idea if the input it is operating on is the input the user intended or not and has no means to warn the user).

This is not a Perl unique problem, it happens in JavaScript:

(() => {
    'use strict';

    /* oops: 1 */
    console.log(Math.round(0.49999999999999999))
})();

It happens in Ruby:

(Proc.new {
    # oops: 1 
    print (0.49999999999999999.round)
}).call()

It happens in PHP:

<?php
(call_user_func(function() {
    /* oops: 1 */
    echo round(0.49999999999999999);
}));
?>

it even happens in C(which is okay to happen, but my gcc does not warn me that the number has not been stored precisely(when specifying specific floating point literals, they had better be stored exactly, or the compiler should warn you that it decided to turn it into another form(e.g. "Your number x cannot be represented in 64 bit/32 bit floating point form, so I converted it to y." ) so you can see if that's okay or not, in this case it is NOT)):

#include <math.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    /* oops: 1 */
    printf("%f.\n", round(0.49999999999999999));

    return 0;
}

Summary:

Is it possible to make Perl show error or warning on implicit conversions of floating numbers, or is this something that Perl5(along with other languages) are incapable of doing at this moment(e.g. The compiler does not go out of its' way to support such warnings/offer a flag to enable such warnings)?

e.g.

warning: the number 0.49999999999999994 is not representable, it has been converted to 0.5. using bigint might solve this. Consider reducing precision of the number.

like image 652
Dmitry Avatar asked Mar 12 '23 02:03

Dmitry


1 Answers

Perhaps use BigNum:

$ perl -Mbignum -le 'print 0.49999999999999994'
0.49999999999999994
$ perl -Mbignum -le 'print 0.49999999999999994+0.1'
0.59999999999999994
$ perl -Mbignum -le 'print 0.49999999999999994-0.1'
0.39999999999999994
$ perl -Mbignum -le 'print 0.49999999999999994+10.1'
10.59999999999999994

It transparently extends precision of Perl floating point and ints to extended precision.

like image 160
dawg Avatar answered May 08 '23 10:05

dawg