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.
|