2013-09-08 4 views
3

Я хотел бы знать, что будет самый быстрый подход по сравнению с поплавками трехзначных places.Say У меня есть что-то вроде этогоСравнить поплавки до трех знаков после запятой

float lhs = 2.567xxxx 
float rhs = 2.566xxxx 

Значение выше должно отличаться друг от друга, и если его что-то вроде это

float lhs = 2.566xxxx 
float rhs = 2.566xxxx 

Они должны быть такими же

Update:

Я пытаюсь следующий

double trunc(double d) 
{ 
    return (d>0) ? floor(d) : ceil(d) ; 
} 


bool comparedigits(float a , float b) 
{ 
    if (trunc(1000.0 * a) == trunc(1000.0 * b)) 
    { 
     return true; 
    } 
    return false; 
} 

    float g = 2.346; 
    float h= 2.34599; 
    bool t = comparedigits(g,h) ; //Not the same and should return false; 

Однако она возвращает истину.

+3

Вы понимаете, что когда вы думаете, что у вас может быть ровно 2,567, у вас может быть 2.56699999 ...? – hvd

+6

[Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой] (http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) –

+0

Извините, исправлено - 3 десятичных знака – MistyD

ответ

9

Чтобы положить конец натиску ответов, которые не так, потому что они позволяют округление, чтобы изменить результаты, вот ответ, который не имеет закругление проблемы, поскольку она использует double для арифметики:

trunc(1000. * lhs) == trunc(1000. * rhs); 

Это работает, потому что 1000. имеет тип double, поэтому другой операнд преобразуется из float в double, а умножение выполняется в формате double. Продукт 1000 с любым значением float точно представлен в double, поэтому ошибки округления нет (предполагается, что 32-битная и 64-разрядная двоичная с плавающей точкой IEEE 754). Затем мы используем trunc для сравнения чисел до (первоначальной) третьей цифры после десятичной точки.

Я не решался дать этот ответ, потому что я не уверен, что это действительно хочет OP. Часто, когда люди приходят в Stack Overflow с просьбой сравнить «до трех знаков после запятой», они не полностью продумали проблему. Полный правильный ответ, возможно, придется подождать, пока у нас не будет разъяснений.

Кроме того, вышесказанное относится только к положительным номерам. Если значения могут быть отрицательными, то на их знаках должно быть проведено предварительное испытание, и false следует вернуть, если они отличаются. (В противном случае, -.0009 будет указано как равное + 0009.)

+1

Не мой нисходящий (я не спускаю вниз), но разве ваше решение не имеет такой же проблемы для float с достаточно большой целочисленной частью? – jxh

+1

@jxh: Нет. Значение «float» - 24 бит. 1000 - 10 бит. Их продукт имеет не более 34 значащих бит. Знак 'double' составляет 53 бит. Существует много места. И диапазон 'double' намного больше, чем диапазон' float', поэтому никогда не происходит переполнения. 'trunc' возвращает свой результат в' double', а не 'int'. –

+0

Вижу, спасибо. +1. – jxh

6

Для значений с плавающей точкой, которые могут поместиться в целое число после x1000 вы можете попробовать:

if (static_cast<int>(lhs*1000.0) == static_cast<int>(rhs*1000.0)) 
{ 
    // Values are near 
} 
else 
{ 
    // They are not identical (maybe!) 
} 

Будьте осторожны с компьютерной точностью представляющих значение с плавающей точкой.


ВАЖНОЕ ОБНОВЛЕНИЕ

Всегда есть номера, которые вы можете не прошедшие код, код Eric Postpischil терпит неудачу так же, как этот код.

Даже преобразование в строку не помогает, мы можем найти числа, которые не могут правильно преобразовать в строки.

Ну, какое решение? Это легко, мы должны определить объем и необходимую точность нашей программы. Мы не можем иметь неограниченную точность в компьютерном мире. What Every Computer Scientist Should Know About Floating-Point Arithmetic

+2

+1, но обратите внимание, что это работает только для значений с плавающей запятой, которые не превышают диапазон 'int'. Это вполне может быть допустимым предположением для ОП. – hvd

+0

@hvd: Согласен, обновлен. – deepmax

+1

В этом тесте указано, что 3.399999999999999911182158029987476766109466552734375 «находится рядом» 3.4000000000000003552713678800500929355621337890625 (потому что они оба производят 3400 при умножении на 1000 с 64-битной двоичной арифметикой IEEE 754 и затем преобразуются в 'int'), но вопрос указывает, что они должны быть представлены как разные, поскольку они отличаются в первых трех цифрах после десятичного знака. –

1

Преобразование float значений строк с полным числом мест (std::numeric_limits<float>::dgits10), то укоротить строку до 3 знаков после запятой, и сравнить полученные строки:

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); 
} 

Различные сравнения предложили умножаются на 100 или 1000 не работают, потому что они будут делать двоичные, а не десятичные округления. Вы можете попытаться добавить 0.5 после умножения и до усечения до int, но есть случаи (хотя и мало), где этот подход все еще не выполняется. Преобразование выше, однако, делает правильные вещи, пока вы не получите больше, чем std::numeric_limits<float>::digit10 цифр. Попытка иметь дело с более десятичными цифрами, чем это число, будет терпеть неудачу, потому что float не может в любом случае представлять столько десятичных цифр.

+2

Как вы знаете, что преобразование float в строку не будет округлять реальное значение? – deepmax

+3

Я не думаю, что это отвечает на вопрос: тестирование показывает, что комментарий M M. намекает на: 2.3451 и 2.3459, не сравниваются с тремя местами. (Вы забыли параметр 'places' в функции' compare', я добавил, что при тестировании.) – hvd

+0

@hvd: Я думаю, что выше ответили на вопрос (кроме глупой опечатки забывания о местах). Это, безусловно, работает со значениями, которые вы цитировали. Каким образом это не работает по вашему мнению? –

2

Если мы предположим, что значение xxxx s в вашем заявлении истинно, не заботитесь, то вам нужно только 7 десятичных знаков точности, тогда будет работать следующая схема.

Чтобы иметь дело с плавающей точкой представительские эффекты из-за ограниченной точности float, вы можете продвигать аргументы, чтобы удвоить, округляется до 7-го знака после запятой, и умножить на 1000. Затем, вы можете использовать modf() для извлечения интегральную часть и сравнить их.

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; 
} 
+3

Это неправильно сообщает, что 1.198999881744384765625 и 1.19900000095367431640625 равны трем десятичным разрядам. –

+0

@EricPostpischil: Хорошо, глядя на реализацию GNU libc, я вижу, что на самом деле он использует небольшую версию произвольной библиотеки точности для достижения точной печати. Я могу подделать это, продвигая двойное внутреннее функционирование. – jxh

+0

Как только вы повысили до уровня «double», зачем беспокоиться об итерациях и причудливых вещах? '1000. * a' - точно правильный результат, без округления. –

0

1) вы пытаетесь сделать равные сравнения с плавающей точкой. Некоторые форматы с плавающей запятой, которые будут работать, но форматы IEEE не будут работать. Вы не можете делать сравнений сравнений. Вам нужно преобразовать этот float в int, а затем сравнить int. С целыми числами (не ограничивая себя до 32 бит или что-то еще здесь) есть только один способ представить каждый номер, чтобы вы могли делать равные сравнения.

2) помните, что математика с плавающей запятой является базой 2, и вы просите сделать базу 10 вещей. поэтому будут проблемы с конверсией, усечение. Кроме того, я снова предполагаю, что вы используете IEEE, что означает, что у вас есть три режима округления (база 2), поэтому вам также придется иметь дело с этим. Вы захотите сделать что-то вроде double_to_integer ((double * 1000.0) +0.5) и сравнить их. Я не удивлюсь, если вы найдете угловые случаи, которые не работают.

Более интересная информация по этой проблеме. Следует отметить, что использование союзов таким образом, не поддерживается стандартом C, но часто просто бывает на работу ...

#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); 
} 

компиляции и запуска

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 

Так делает 1000 * х в одиночном математика вместо двойной математики, как представляется, устраняет проблему

1000,0 * a - смешанный режим. 1000.0 является двойным по стандарту C, если не указано, что он один. И a является одиночным, поэтому a преобразуется в double, математика выполняется как двойная, а затем подается на двойную функцию. 1000.0F является одиночным, a является одиночным, поэтому умножение выполняется как единая математика, затем оно преобразуется в double. Поэтому, возможно, настоящая проблема заключается в преобразовании и округлении g и h в double. Нужно будет больше копать в различиях мантиссы ...

Я думаю, что ключ в этом, двойной раз один 1000.0 * x результаты

trunc 
0x40A25200_00000000 
0x40A25200_00000000 

Если они равны, то все, что вы делаете, будет одинаковым. Когда это один раз один, а затем преобразованный в двойной, они различаются.

trunc 
0x40A25400_00000000 
0x40A25200_00000000 

и это заставляет работать ваш код (для этих двух конкретных значений).

false 
+0

+1 Ваш второй абзац о базе представлений разъясняет многие неточности в преобразованиях. – deepmax

+1

Ваш первый абзац абсурд от начала до конца. Равенство с плавающей точкой IEEE 754 работает очень хорошо, и для любого бинарного формата IEEE 754 каждое конечное значение с плавающей запятой (кроме '+ 0.0' и' -0.0') представляет собой рациональное, которое не представлено никаким другим другим значением в том же формате. –

+0

И добавьте 0.5 для вычисления ближайшего целого с усечением не работает очень хорошо: http://blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1 (в двойной точности константы равны 0.999999999999999889 'для первого проблемного значения и 2^52..2^53 для диапазона значений, которые точно разделены). –

Смежные вопросы