I have the following code, which is causing me a weird double to long conversion problem. Under the hood, logCpof
just writes to a special log file, using vprintf
. In my example below, the top one works, but the bottom one does not. That said, I have other examples where it is reversed, or where both fail.
psMbo->mbonum4 = (long)(psXmlRequest->dTotalOutstanding * 100);
logCpof( psMbo->mbocpo, "Num4 %ld from %.*s, %f * 100 = %f (%ld)",
psMbo->mbonum4,
str_len(psXmlRequest->zTotalOutstanding), psXmlRequest->zTotalOutstanding,
psXmlRequest->dTotalOutstanding,
psXmlRequest->dTotalOutstanding * 100,
(long)(psXmlRequest->dTotalOutstanding * 100 ) );
...
psMbo->mbonum5 = (long)(psXmlRequest->dTotalPriceSetPresentmentMoney * 100);
logCpof( psMbo->mbocpo, "Num5 %ld from %.*s, %f * 100 = %f (%ld)",
psMbo->mbonum5,
str_len(psXmlRequest->zTotalPriceSetPresentmentMoney),
psXmlRequest->zTotalPriceSetPresentmentMoney,
psXmlRequest->dTotalPriceSetPresentmentMoney,
psXmlRequest->dTotalPriceSetPresentmentMoney * 100,
(long)(psXmlRequest->dTotalPriceSetPresentmentMoney * 100 ) );
This gives the following log:
xmlCli 184906:Num4 34079 from 340.79, 340.790000 * 100 = 34079.000000 (34079)
xmlCli 184906:Num5 37294 from 372.95, 372.950000 * 100 = 37295.000000 (37294)
So how does double 37295.000 become long 27394? The conversion from double to long seems to be decremented by 1. As I implied above, it does not happen with all doubles, just with some doubles. For instance, double 34079.00 becomes long 34079, as I would expect. The LONG_MAX is 2147483647, so my new long is considerably smaller than that. As such, I don't see how it could be hitting some weird max.
I did run my code through valgrind
, but that did not reveal any memory issues related to this section of code. That said, it did consistently work better. Any ideas?
Edit 4/15/2022 to answer comments:
First of all, some things that are not obvious from my code snippet:
psXmlRequest->dTotalOutstanding = strtod(
psXmlRequest->zTotalOutstanding, &pzTail );
psXmlRequest->dTotalPriceSetPresentmentMoney = strtod(
psXmlRequest->zTotalPriceSetPresentmentMoney, &pzTail );
zTotalOutstanding
and zTotalPriceSetPresentmentMoney
are both 101 character text fields filled from an XML file. Not a huge deal, just not obvious from my code, and this is why I use %.*s
in my log statement.
Secondly, and also not obvious from the code, but mbonum4 and mbonum5 are long fields (actually goes to a MySql table with columns of type int(11)
, but this is a side issue). No more doubles in the structure, and we need to preserve 2 decimal places, so thus the * 100
. Not elegant, but works just fine most of the time. This is one of the few situations where it does not work.
Finally, this is my changed code based on @Eric Postpischil comments:
psMbo->mbonum4 = (long)(psXmlRequest->dTotalOutstanding * 100);
logCpof( psMbo->mbocpo,
"Num4 %ld from %.*s, %f (AKA %.99g) * 100 = %f (%ld) or %.99g",
psMbo->mbonum4,
str_len(psXmlRequest->zTotalOutstanding), psXmlRequest->zTotalOutstanding,
psXmlRequest->dTotalOutstanding,
psXmlRequest->dTotalOutstanding,
psXmlRequest->dTotalOutstanding * 100,
(long)(psXmlRequest->dTotalOutstanding * 100 ),
psXmlRequest->dTotalOutstanding * 100 );
psMbo->mbonum5 = (long)(psXmlRequest->dTotalPriceSetPresentmentMoney * 100);
logCpof( psMbo->mbocpo,
"Num5 %ld from %.*s, %f (AKA %.99g) * 100 = %f (%ld) or %.99g",
psMbo->mbonum5,
str_len(psXmlRequest->zTotalPriceSetPresentmentMoney),
psXmlRequest->zTotalPriceSetPresentmentMoney,
psXmlRequest->dTotalPriceSetPresentmentMoney,
psXmlRequest->dTotalPriceSetPresentmentMoney,
psXmlRequest->dTotalPriceSetPresentmentMoney * 100,
(long)(psXmlRequest->dTotalPriceSetPresentmentMoney * 100 ),
psXmlRequest->dTotalPriceSetPresentmentMoney * 100 );
And here are the results, as requested:
xmlCli 135817:Num4 34079 from 340.79, 340.790000 (AKA 340.79000000000002046363078989088535308837890625) * 100 = 34079.000000 (34079) or 34079
xmlCli 135817:Num5 37294 from 372.95, 372.950000 (AKA 372.94999999999998863131622783839702606201171875) * 100 = 37295.000000 (37294) or 37295
So I guess the 372.94999999999998863131622783839702606201171875
proves that the double has extra information I am not seeing. So, based on other comments, I guess I first need to round to 2 decimal places (normal rounding logic), and then * 100
? Or is there something else obvious I am missing.
CodePudding user response:
My fix is as follows, based on the comments, especially from @Eric Postpischil:
psMbo->mbonum4 = (long)RRND(psXmlRequest->dTotalOutstanding * 100, 0);
logCpof( psMbo->mbocpo,
"Num4 %ld from %.*s, %.99g * 100 = %f, RRND %ld, %f",
psMbo->mbonum4,
str_len(psXmlRequest->zTotalOutstanding), psXmlRequest->zTotalOutstanding,
psXmlRequest->dTotalOutstanding,
psXmlRequest->dTotalOutstanding * 100,
(long)RRND(psXmlRequest->dTotalOutstanding * 100, 0 ),
RRND(psXmlRequest->dTotalOutstanding * 100, 0 ));
...
psMbo->mbonum5 = (long)RRND(
psXmlRequest->dTotalPriceSetPresentmentMoney * 100, 0);
logCpof( psMbo->mbocpo,
"Num5 %ld from %.*s, %.99g * 100 = %f, RRND %ld, %f",
psMbo->mbonum5,
str_len(psXmlRequest->zTotalPriceSetPresentmentMoney),
psXmlRequest->zTotalPriceSetPresentmentMoney,
psXmlRequest->dTotalPriceSetPresentmentMoney,
psXmlRequest->dTotalPriceSetPresentmentMoney * 100,
(long)RRND(psXmlRequest->dTotalPriceSetPresentmentMoney * 100, 0 ),
RRND(psXmlRequest->dTotalPriceSetPresentmentMoney * 100, 0 ));
where RRND function is defined as follows:
double RRND( double fnbr, double fexp )
{
double scale = pow( (double)10.0, fexp );
return( ( floor((double)( fnbr * scale ) .5 ) ) / scale );
}
My final test results were:
xmlCli 180051:Num4 34079 from 340.79, 340.79000000000002046363078989088535308837890625 * 100 = 34079.000000, RRND 34079, 34079.000000
xmlCli 180051:Num5 37295 from 372.95, 372.94999999999998863131622783839702606201171875 * 100 = 37295.000000, RRND 37295, 37295.000000
It is weird because I have used code like (long)<double value> * 100
many times with no visible problems. I suspect in all those cases, we did not care if it was slightly off, and since it only happens in certain cases, we just never noticed. We care in this case, so we noticed it, and were forced to fix it. As per the advice in the comments, rounding after multiplying by 100 was the key.
CodePudding user response:
When you convert a floating point number to an integer (including a long), the C and C specs specify that the conversion truncates towards 0, rather than rounding to the nearest. When you print a floating point number with limited precision (as you've done here with %f -- which prints to (just) 6 decimal places) it will round towards the nearest.
As a result, when you have a floating point number that is eg 5.9999999 the printf with %f will print 6.000000, while a conversion to int will give 5.
If you want to convert a float (or double) to the nearest integer you should use round
-- eg: (long)round(psXmlRequest->dTotalOutstanding * 100 )
in your example code. The round
function will round the double to the nearest integer, which will then be converted to a long