I wanted to know what would be the fastest approach of comparing floats to three decimal places.Say I have something like this
float lhs = 2.567xxxx
float rh
1) you are trying to do equals comparisons with floating point. Some floating point formats that will work, but IEEE formats will not work. You cannot do equals comparisons. You need to convert that float to an int then do an int compare. With integers (not limiting myself to 32 bit or anything here) there is only one way to represent each number so you can do equals comparisons.
2) remember the floating point math is base 2 and you are asking to do base 10 stuff. so there are going to be conversion issues, truncation. Also I again assume you are using IEEE which means you have three rounding modes (base 2) so you have to deal with that as well. You will want to do some sort of double_to_integer((double*1000.0)+0.5) and compare those. I would not be surprised if you find corner cases that dont work.
More interesting information on this problem. Note that use of unions in this manner is not supported by the C standard, but often just happens to work...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
double trunc(double d)
{
return (d>0) ? floor(d) : ceil(d) ;
}
int comparedigits(float a , float b)
{
if (trunc(1000.0 * a) == trunc(1000.0 * b))
{
return 1;
}
return 0;
}
union
{
unsigned int ul;
float f;
} fun;
union
{
unsigned int ul[2];
double d;
} dun;
int main ( void )
{
float g;
float h;
int t;
g = 2.346;
h = 2.34599;
t = comparedigits(g,h);
printf("%u\n",t);
printf("raw\n");
fun.f=g; printf("0x%08X\n",fun.ul);
fun.f=h; printf("0x%08X\n",fun.ul);
dun.d=g; printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
dun.d=h; printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
printf("trunc\n");
dun.d=trunc(1000.0 * g); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
dun.d=trunc(1000.0 * h); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
printf("trunc\n");
dun.d=trunc(1000.0F * g); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
dun.d=trunc(1000.0F * h); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
printf("floor\n");
dun.d=floor(1000.0 * g); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
dun.d=floor(1000.0 * h); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
printf("ceil\n");
dun.d=ceil(1000.0 * g); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
dun.d=ceil(1000.0 * h); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
printf("%u\n",(unsigned int)(g*1000.0));
printf("%u\n",(unsigned int)(h*1000.0));
if (trunc(1000.0F * g) == trunc(1000.0F * h))
{
printf("true\n");
}
else
{
printf("false\n");
}
return(0);
}
compile and run
gcc test.c -o test -lm
./test
1
raw
0x401624DD
0x401624B3
0x4002C49B_A0000000
0x4002C496_60000000
trunc
0x40A25200_00000000
0x40A25200_00000000
trunc
0x40A25400_00000000
0x40A25200_00000000
floor
0x40A25200_00000000
0x40A25200_00000000
ceil
0x40A25400_00000000
0x40A25400_00000000
2345
2345
false
So doing the 1000 * x in single math instead of double math appears to fix the problem
1000.0 * a is mixed mode. The 1000.0 is a double by the C standards unless specified to be a single. And a is a single, so a is converted to a double the math is done as double then it is fed to a double function. 1000.0F is a single, a is a single, so the multiply is done as single math, then it is converted to double. So perhaps the real issue is in the conversion and rounding of g and h to a double. Would have to dig more into the mantissa differences...
I think the key is this, the double times single 1000.0 * x results
trunc
0x40A25200_00000000
0x40A25200_00000000
If those are equal then anything you do the same number will come out the same. When it is a single times a single then converted to a double, they differ.
trunc
0x40A25400_00000000
0x40A25200_00000000
and that makes your code work (for those two specific values).
false
For float values which can fit into an integer after x1000
you can try:
if (static_cast<int>(lhs*1000.0) == static_cast<int>(rhs*1000.0))
{
// Values are near
}
else
{
// They are not identical (maybe!)
}
Be careful of computer accuracy in representing float value.
IMPORTANT UPDATE
Always there're numbers which can fail a code, Eric Postpischil's code fails as same as this code.
Even converting to string doesn't help, we can find numbers which can not convert to strings correctly.
Well, what is the solution? It's easy, we must define scope and needed accuracy of our program. We can not have unlimited precision in computer world. What Every Computer Scientist Should Know About Floating-Point Arithmetic
To put a stop to the onslaught of answers that are wrong because they allow rounding to alter the results, here is an answer that does not have the rounding problem, because it uses double
for the arithmetic:
trunc(1000. * lhs) == trunc(1000. * rhs);
This works because 1000.
has type double
, so the other operand is converted from float
to double
, and the multiplication is performed in the double
format. The product of 1000 with any float
value is exactly representable in double
, so there is no rounding error (assuming IEEE 754 32-bit and 64-bit binary floating-point). Then we use trunc
to compare the numbers up to the (original) third digit after the decimal point.
I hesitated to provide this answer because I am not sure it is what the OP really wants. Often when people come to Stack Overflow with a request for comparing “to three decimal places”, they have not entirely thought through the problem. A complete correct answer may have to wait until we have clarification.
Also, the above is for positive numbers only. If the values may be negative, then a prior test should be performed on their signs, and false
should be returned if they differ. (Otherwise, –.0009 would be reported as equal to +.0009.)
If we assume that the xxxx
s in your statement are true don't cares, that is you only care about 7 decimal places of precision, then the following scheme will work.
To deal with floating point representation effects due to the limited precision of float
, you can promote the arguments to double, rounded to the 7th decimal place, and multiply by 1000. Then, you can use modf() to extract the integral part and compare them.
bool equals_by_3_decimal_places (float a, float b) {
double ai, bi;
modf((.00000005 + a) * 1000, &ai);
modf((.00000005 + b) * 1000, &bi);
return ai == bi;
}
Convert the float
values to strings with the full number of places (std::numeric_limits<float>::dgits10
), then truncate the string to 3 decimal places, and compare the resulting strings:
std::string convert(float value, int places) {
if (value == 0) {
return "0";
}
int digits(std::numeric_limits<float>::digits10 - std::log(value) / std::log(10));
digits = std::max(0, digits);
std::ostringstream out;
out << std::fixed << std::setprecision(digits) << value;
std::string rc(out.str());
return places < digits? rc.substr(0, rc.size() - (digits - places)): rc;
}
bool compare(float f1, float f2) {
return convert(f1, 3) == convert(f2, 3);
}
The various comparisons proposed multiplying by 100 or 1000 don't work because they will do binary rather than decimal rounding. You could try to add 0.5 after multiplying and before truncating to int
but there are cases (although few) where this approach still fails. The conversion above, however, does the right thing as long as you don't end up with more than std::numeric_limits<float>::digit10
digits. Trying to deal with more decimal digits than this number will fail because the float
can't represent as many decimal digits correctly anyway.