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 (Abbot) on Mar 21, 2023 at 15:13 UTC | |
by bibliophile (Prior) on Mar 21, 2023 at 18:37 UTC |