Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re: Yet again: floats are not expressed accurately

by pryrt (Abbot)
on Mar 21, 2023 at 14:36 UTC ( [id://11151104]=note: print w/replies, xml ) Need Help??


in reply to Yet again: floats are not expressed accurately

float x = 226.0; if( ABS(x-226.0)>0.00000001 ){ printf("ajaja\n"); }

What you've shown here is 226.0 - 226.0, which may or may not be the same thing as 2.26*100.0 - 226.0; it would have been a more meaningful delta-check if you'd actually done the multiplication from the small float. However, when I tried the latter with the gcc that came with Strawberry, it still didn't show the "ajaja" message, so it's still closer than 0.00000001. That is because a c float is 32bit, so the 32bit ULP of 226.0 is 1.5259e-5. But you showed:

perl -V:nvsize -V:nvtype nvsize='8'; nvtype='double';

... so perl is currently using a c double for NV... which means your c code should have been using doubles to replicate it. (You might ask "why did it work on previous versions?" A version of 2.25 doesn't have the rounding problem, because it's exactly representable in a c-double/perl-NV. And the version before that was 2.24, and 2.24*100=224.00000000000003 as a double, so int(that) rounds down to 224, so you just hadn't noticed it on that version.)

The ULP_64bit_double(226.0) is 2.842e-14, so checking against 1e-8 is not sufficient. Updating the c-code to also show a double variable 2.26*100.0 and compare the error to ULP(226.0):

/* gcc a.c */ #include <stdio.h> #define ABS(A) ((A)>0?(A):(-(A))) int main(void){ float version = 2.26; float newversion = version + 0.01; printf("version=%f, newversion=%f\n", version, newversion); /* bliako's "check" */ printf("2.26*100=%.8f\n", 2.26*100); float x = 226.0; if( ABS(x-226.0)>0.00000001 ){ printf("bliako's check has ABS erro +r\n"); } else {printf("bliako's check OK\n");} /* closer check: check the multiplied value against the hardcoded +226.0 */ /* as a 32bit float, the ULP(226.0) is (2**7)*(2**-23) = 2**-16 = + 15.259e-6 = 1.5259e-5, so a 1 ULP error would be >1.525e-5, so also +greater than 0.00000001 */ x = 2.26 * 100.0; printf("2.26*100=%.16f\n", x); if( ABS(x-226.0)>0.00000001 ){ printf("float-ULP check has ABS err +or\n"); } else {printf("float-ULP check OK\n");} /* but perl with nvtype=double requires a check against a c double +, where the ULP(226.0) = 2.842e-14 */ double d = 2.26 * 100.0; printf("2.26*100=%.16lf\n", d); if( ABS(d-226.0)>=2.842e-14 ){ printf("double-ULP check has ABS er +ror\n"); } else {printf("double-ULP check OK\n");} }

gives output

version=2.260000, newversion=2.270000 2.26*100=226.00000000 bliako's check OK 2.26*100=226.0000000000000000 float-ULP check OK 2.26*100=225.9999999999999700 double-ULP check has ABS error

You will notice that the double shows the same issue as the perl v5.36.0 which you showed. (Please note, as I said in chatterbox yesterday, that this is dependent on perl's compilation choices (nvsize/nvtype), not on v5.36.0; the Strawberry Perl v5.32.1 that I use also has double nvtype, so it shows the same behavior with 2.26*100.0 that your v5.36.0 shows.

So, the advice to use decimal cents to do calculations in cents instead of using floating dollars is sound but how do I get to the decimal cents in the first place?

Like I suggested in the chatterbox yesterday, one possible way is perl -MPOSIX=round -le "printf(qq(%.2f), (round(2.26 * 100.0) + 1.0)/100.0)" -- this uses POSIX's round() function to round-to-nearest after you've done the multiplication. Or, to put it another way, my $cents = POSIX::round(2.26*100);, then do the remaining calculations with the cents.

Or treat the number as a string, and, as syphilis implied, split the string on the decimal point, and just do the cents manipulation on the righthand side, after error checking and appropriate manipulation, depending on whether you mean `1.1` to be 1*100+1 or 1*100+10, and whether you want to carry from 1.99 to 2.00 or not.

Replies are listed 'Best First'.
Re^2: Yet again: floats are not expressed accurately
by bliako (Monsignor) on Mar 21, 2023 at 15:13 UTC

    Thank you pryrt. One reason to pursue this further (i.e. bothering all of you here) was that C was not showing it, but as you showed, my mistake! was not to use double precision like my perl does. I have used your POSIX::round suggestion yesterday and it did work. Also, with Perl's easy numerical <-> string conversions it's easy to go to cents by a string-manipulation route (remove and add the dot) and avoid even getting the cents wrong as was in my case. So, yes thanks and thanks to all for their input and suggestions.

      A unique position where it both makes cents, and doesn't make scents.... :-)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://11151104]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (3)
As of 2024-04-16 21:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found