3

浮動小数点数を小数点以下 3 桁まで比較する最も速い方法は何かを知りたかったのですが、次のようなものがあるとします。

float lhs = 2.567xxxx
float rhs = 2.566xxxx

上記は異なるはずで、このようなものであれば

float lhs = 2.566xxxx
float rhs = 2.566xxxx

それらは同じであるべきです

アップデート:

私は次のことを試しています

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;

しかし、それはtrueを返しています。

4

5 に答える 5

14

double丸めによって結果が変わる可能性があるために間違っている答えの猛攻撃を止めるために、算術に使用するため、丸めの問題がない答えを次に示します。

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

これ1000.は typedoubleを持っているため機能するため、他のオペランドは から に変換されfloatdouble乗算はdouble形式で実行されます。1000 と任意の値の積floatは で正確に表現doubleできるため、丸め誤差はありません (IEEE 754 32 ビットおよび 64 ビット 2 進浮動小数点を想定)。次にtrunc、小数点以下の (元の) 3 桁目までの数値を比較するために使用します。

OPが本当に望んでいるものかどうかわからないため、この回答を提供することをためらいました。多くの場合、「小数点以下 3 桁まで」の比較を求めて Stack Overflow に来る人は、問題を完全には考えていません。完全な正解は、明確になるまで待たなければならない場合があります。

また、上記は正の数のみです。値が負の可能性がある場合は、それらの符号について前もってテストを実行し、false異なる場合はそれを返す必要があります。(それ以外の場合、–.0009 は +.0009 と等しいと報告されます。)

于 2013-09-08T18:04:11.567 に答える
5

x1000試した後に整数に収まる浮動小数点値の場合:

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

float 値を表す際には、コンピューターの精度に注意してください。


重要な更新

コードが失敗する可能性のある数字は常にあります。Eric Postpischil のコードは、このコードと同じように失敗します。

文字列に変換しても役に立ちません。文字列に正しく変換できない数値を見つけることができます。

さて、解決策は何ですか?簡単です。プログラムの範囲と必要な精度を定義する必要があります。コンピュータの世界では、無限の精度を持つことはできません。すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと

于 2013-09-08T16:58:47.953 に答える
2

xxxxステートメントの s が true であり、気にしない、つまり小数点以下 7 桁の精度のみを気にすると仮定すると、次のスキームが機能します。

の精度の制限による浮動小数点表現の影響に対処するにfloatは、引数を double に昇格し、小数点以下 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;
}
于 2013-09-08T17:20:28.817 に答える
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 を掛けることで提案されているさまざまな比較は、10 進数ではなく 2 進数で丸められるため、機能しません。乗算後、切り捨ての前に 0.5 を追加しようとすることもできますがint、このアプローチがまだ失敗する場合があります (少数ではありますが)。std::numeric_limits<float>::digit10ただし、上記の変換は、最終的に桁数を超えない限り、正しいことを行います。この数値よりも多くの 10 進数を処理しようfloatとしても失敗します。

于 2013-09-08T17:03:56.997 に答える
-1

1) 浮動小数点との等号比較を行おうとしています。一部の浮動小数点形式は機能しますが、IEEE 形式は機能しません。等しい比較はできません。その float を int に変換してから、int 比較を行う必要があります。整数の場合 (ここでは 32 ビットなどに限定されません)、各数値を表す方法は 1 つしかないため、等号比較を行うことができます。

2) 浮動小数点演算は基数 2 であり、基数 10 の処理を​​要求していることを思い出してください。そのため、変換の問題、切り捨てが発生します。また、IEEE を使用していると仮定します。つまり、3 つの丸めモード (基数 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 * x を実行すると、問題が解決するようです

1000.0 * a は混合モードです。1000.0 は、単精度と指定されていない限り、C 標準では倍精度です。a は single であるため、a は double に変換され、数学は double として実行され、double 関数に渡されます。1000.0F は単数、a は単数なので、乗算は単数演算として行われ、次に倍精度に変換されます。したがって、おそらく本当の問題は、g と h を double に変換して丸めることにあります。仮数の違いをもっと掘り下げる必要があります...

鍵はこれだと思います、double 掛ける single 1000.0 * x 結果

trunc
0x40A25200_00000000
0x40A25200_00000000

それらが等しい場合、同じ数を何をしても同じ結果になります。シングル倍のシングルをダブルに変換すると、それらは異なります。

trunc
0x40A25400_00000000
0x40A25200_00000000

これにより、コードが機能します(これらの2つの特定の値に対して)。

false
于 2013-09-08T19:20:34.063 に答える