Compare floats to three decimal places

后端 未结 5 2041
不思量自难忘°
不思量自难忘° 2021-01-14 03:44

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         


        
相关标签:
5条回答
  • 2021-01-14 04:15

    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
    
    0 讨论(0)
  • 2021-01-14 04:17

    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

    0 讨论(0)
  • 2021-01-14 04:19

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

    0 讨论(0)
  • 2021-01-14 04:21

    If we assume that the xxxxs 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;
    }
    
    0 讨论(0)
  • 2021-01-14 04:32

    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.

    0 讨论(0)
提交回复
热议问题