Hi,
I found the ut_compare() function, as currently existing the the udunits2
library, to be too strict for my needs.
In particular, for some units, the comparison between the original unit and
a back-and-forth transformation to text expression fails.
In other words : for some values of unit, ut_compare(unit,
(ut_parse(ut_format(unit))) != 0.
I know that a decimal textual representation of a real number cannot be
exact, but I believe that we can safely assume that two units whose relative
difference is less than 10^-8 or 10^-10 are identical.
So, I wrote my own, more tolerant, ut_compare function (see attached source
code).
My questions are :
- is this pernickety behaviour intended, or did someone just
wrote the test "a==b" for floating-point values instead of the usual
approximate test ?
- Am I the only one interested in a more lenient version of
ut_compare() ? Would it be a good idea to add a ut_tolerant_compare() function
to the udunits2 library ?
Regards,
Bruno.
--
Bruno Piguet
Météo-France
Équipe GMEI/TRAMM
CNRM/GAME, UMR3589 CNRS/Météo-France
Fixe : +33 561079659
Fax : +33 561079627
Postal : 42 Av. G. Coriolis
31057 Toulouse CEDEX 1
/**
* Demonstration of the strict behaviour of ut_compare(), (for some values
* of unit, ut_compare(unit, (ut_parse(ut_format(unit))) != 0), and
* of a more lenient version.
*
* compile with :
* gcc -O pb_compare.c -ludunits2 -o pb_compare
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <udunits2/udunits2.h>
static ut_system *unitSystem;
int
my_ut_compare_pragmatic (const ut_unit * unit1, const ut_unit * unit2)
{
int CodRet;
ut_unit *ratio_ut_unit;
double ratio_double;
char buf[120];
char *endptr;
CodRet = ut_compare (unit1, unit2);
if (CodRet == 0)
return CodRet;
/* "This function returns a non-zero value if conversion is possible;
* otherwise, 0 is returned" */
CodRet = ut_are_convertible (unit1, unit2);
/* if conversion impossible, exit */
if (CodRet == 0)
return -5;
/* let's see if conversion ratio is close to 1 */
ratio_ut_unit = ut_divide (unit1, unit2);
if (ratio_ut_unit == NULL)
return -6;
/* Don't know how tho get the ration from an ut_unit variable.
* So, let's format it and parse the resulting string */
CodRet = ut_format (ratio_ut_unit, buf, sizeof (buf), UT_ASCII);
ut_free (ratio_ut_unit);
ratio_ut_unit = NULL;
if ((CodRet < 0) || (CodRet >= (int) sizeof (buf)))
{
return -7;
}
/* the end must be " 1" (meaning dimensionless) */
if ((strlen (buf) > 2) && (!strcmp (buf + strlen (buf) - 2, " 1")))
{
buf[strlen (buf) - 2] = 0;
ratio_double = strtod (buf, &endptr);
if ((*endptr == 0) && (fabs (1.0 - ratio_double) < 1.0e-8))
{
/* Approximately equal --> OK ! */
return 0;
}
}
return -8;
}
void
test_back_forth (const char *unit_str)
{
ut_unit *unit_1, *unit_2;
char buf[120];
int len, cmp_cod;
unit_1 = ut_parse (unitSystem, unit_str, UT_ASCII);
if (unit_1 == NULL)
{
fprintf (stderr, "Error %d parsing : %s\n", ut_get_status (), unit_str);
return;
}
len = ut_format (unit_1, buf, sizeof (buf), UT_ASCII);
if ((len < 0) || (len >= (int) sizeof (buf)))
{
fprintf (stderr, "Error %d writing unit\n", ut_get_status ());
ut_free (unit_1);
return;
}
fprintf (stdout, "%s formatted as : %s\n", unit_str, buf);
unit_2 = ut_parse (unitSystem, buf, UT_ASCII);
if (unit_2 == NULL)
{
fprintf (stderr, "Error %d parsing : %s\n", ut_get_status (), buf);
ut_free (unit_1);
return;
}
cmp_cod = ut_compare (unit_1, unit_2);
fprintf (stdout, " ut_compare(%s, %s) = %d\n", unit_str, buf, cmp_cod);
cmp_cod = my_ut_compare_pragmatic (unit_1, unit_2);
fprintf (stdout, " my_ut_compare(%s, %s) = %d\n", unit_str, buf,
cmp_cod);
ut_free (unit_1);
ut_free (unit_2);
}
int
main (int argc, char *argv[])
{
const char *unit_tab[] = { "degree",
"kt",
"ft/min",
"mm/h",
"l/min"
};
unsigned long nb_unit = sizeof (unit_str) / sizeof (*unit_str);
unsigned long i;
unitSystem = ut_read_xml (NULL);
if (unitSystem == NULL)
return 1;
for (i = 0; i < nb_unit; i++)
test_back_forth (unit_tab[i]);
ut_free_system (unitSystem);
return 0;
}