Tengo el siguiente código, que me está causando un extraño problema de conversión de doble a largo. Debajo del capó, logCpof
solo escribe en un archivo de registro especial, usando vprintf
. En mi ejemplo a continuación, el superior funciona, pero el inferior no. Dicho esto, tengo otros ejemplos donde se invierte, o donde ambos fallan.
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 ) );
Esto da el siguiente registro:
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)
Entonces, ¿cómo el doble 37295.000 se convierte en largo 27394? La conversión de doble a largo parece disminuir en 1. Como insinué anteriormente, no sucede con todos los dobles, solo con algunos dobles. Por ejemplo, el doble 34079.00 se convierte en largo 34079, como era de esperar. El LONG_MAX es 2147483647, por lo que mi nuevo largo es considerablemente más pequeño que eso. Como tal, no veo cómo podría estar alcanzando un máximo extraño.
Ejecuté mi código valgrind
, pero eso no reveló ningún problema de memoria relacionado con esta sección del código. Dicho esto, siempre funcionó mejor. ¿Algunas ideas?
Edite el 15/04/2022 para responder a los comentarios:
En primer lugar, algunas cosas que no son obvias a partir de mi fragmento de código:
psXmlRequest->dTotalOutstanding = strtod(
psXmlRequest->zTotalOutstanding, &pzTail );
psXmlRequest->dTotalPriceSetPresentmentMoney = strtod(
psXmlRequest->zTotalPriceSetPresentmentMoney, &pzTail );
zTotalOutstanding
y zTotalPriceSetPresentmentMoney
ambos son campos de texto de 101 caracteres rellenados a partir de un archivo XML. No es un gran problema, simplemente no es obvio en mi código, y es por eso que lo uso %.*s
en mi declaración de registro.
En segundo lugar, y tampoco es obvio por el código, pero mbonum4 y mbonum5 son campos largos (en realidad van a una tabla MySql con columnas de tipo int(11)
, pero este es un problema secundario). No más dobles en la estructura, y necesitamos preservar 2 lugares decimales, por lo que el * 100
. No es elegante, pero funciona bien la mayor parte del tiempo. Esta es una de las pocas situaciones en las que no funciona.
Finalmente, este es mi código modificado basado en los comentarios de @Eric Postpischil:
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 );
Y aquí están los resultados, según lo solicitado:
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
Así que supongo 372.94999999999998863131622783839702606201171875
que prueba que el doble tiene información adicional que no estoy viendo. Entonces, según otros comentarios, supongo que primero necesito redondear a 2 decimales (lógica de redondeo normal) y luego * 100
. O hay algo más obvio que me estoy perdiendo.
Solución del problema
Mi solución es la siguiente, según los comentarios, especialmente de @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 ));
donde la función RRND se define de la siguiente manera:
double RRND( double fnbr, double fexp )
{
double scale = pow( (double)10.0, fexp );
return( ( floor((double)( fnbr * scale ) +.5 ) ) / scale );
}
Los resultados finales de mi prueba fueron:
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
Es extraño porque he usado código (long)<double value> * 100
muchas veces sin problemas visibles. Sospecho que en todos esos casos, no nos importó si estaba un poco mal, y dado que solo sucede en ciertos casos, simplemente nunca nos dimos cuenta. Nos preocupamos en este caso, así que lo notamos y nos vimos obligados a arreglarlo. Según el consejo en los comentarios, la clave fue redondear después de multiplicar por 100.
No hay comentarios:
Publicar un comentario