1
  • コンパイラ: GCC 4.7.2 (Debian 4.7.2-5)
  • プラットフォーム: Linux 3.2.0 x86 (Debian 7.1)

独自の文字列を float 変換関数に記述しようとしています。これは基本的に の安価なぼったくりですがstrtof()、正確に模倣することはできませんstrtof()。私の機能がstrtof()正確に模倣されるとは思っていませんが、なぜどこで違うのか知りたいです。いくつかの異なる文字列をテストしましたが、次の文字列は、 が関数に与えられたときと、 に与えられたとき、strtof()および を使用して出力されたときに、異なる値を持つことがわかりましたprintf("%.38f"))

  1. 1234.5678
  2. 44444.44444
  3. 333.333
  4. 777.777

なぜこれが起こるのですか?(また、他の間違いを指摘したり、異なる値を持つ他の文字列を教えてください (すべてを見つける方法はありません)。)

#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <math.h>

int dec_to_f(char *dec, float *f)
{
int i = 0;
float tmp_f = 0;

if(dec == NULL) return 1;

if(f == NULL) return 2;

if(dec[i] == '\000') return 3;

if(dec[i] == '-')
{
    i++;

    if(dec[i] == '\000') return 3;

    for(; dec[i] != '\000'; i++)
    {
        if(dec[i] == '.')
        {
            float dec_place = 10;
            int power_of_ten = 1;

            for(i++; dec[i] != '\000'; i++, power_of_ten++, dec_place *= 10)
            {
                if(dec[i] >= '0' && dec[i] <= '9')
                {
                    if(power_of_ten > FLT_MAX_10_EXP) return 4;
                    else tmp_f -= (dec[i] - '0') / dec_place;
                }
                else return 5;
            }

            break;
        }

        if(dec[i] >= '0' && dec[i] <= '9')
        {
            tmp_f = tmp_f * 10 - (dec[i] - '0');
            if(!isfinite(tmp_f)) return 6;
        }
        else return 5;
    }
}
else
{
    if(dec[i] == '+')
    {
        if(dec[i+1] == '\000') return 3;
        else i++;
    }

    for(; dec[i] != '\000'; i++)
    {
        if(dec[i] == '.')
        {
            float dec_place = 10;
            int power_of_ten = 1;

            for(i++; dec[i] != '\000'; i++, power_of_ten++, dec_place *= 10)
            {
                if(dec[i] >= '0' && dec[i] <= '9')
                {
                    if(power_of_ten > FLT_MAX_10_EXP) return 7;
                    else tmp_f += (dec[i] - '0') / dec_place;
                }
                else return 5;
            }

            break;
        }

        if(dec[i] >= '0' && dec[i] <= '9')
        {   
            tmp_f = tmp_f * 10 + (dec[i] - '0');
            if(!isfinite(tmp_f)) return 8;
        }
        else return 5;
    }
}

*f = tmp_f;
return 0;
    }

int main()
{
printf("FLT_MIN = %.38f\n", FLT_MIN);
printf("FLT_MAX = %f\n", FLT_MAX);
float f = 0;
int return_value = 0;
char str[256];

printf("INPUT = ");
scanf("%s", str);

return_value = dec_to_f(str, &f);

printf("return_value = %i\nstr = \"%s\"\nf = %.38f\nstrtof = %.38f\n", return_value, str, f, strtof(str, NULL));
}
4

4 に答える 4

3

10 進数を 2 進数に、またはその逆に正しい丸めを使用して変換することは複雑であり、浮動小数点演算の詳細な知識が必要であり、注意が必要です。

変換が難しい理由はいくつかあります。それらの2つは次のとおりです。

  • 浮動小数点を使用して計算を実行すると、それらの計算で丸め誤差が発生することがよくあります。計算が慎重に設計されていない場合、それらの丸め誤差が最終結果に影響します。
  • 一部の入力は、丸めポイント (表現可能な 2 つの最も近い値がほぼ同じ距離にあるため、丸めが変化するポイント) に非常に近くなります。例として、 1.30000001192092895507812 xを考えてみましょう。xが 4 の場合、結果は 1.2999999523162841796875 になります。6 の場合、結果は 1.30000007152557373046875 になります。それでも、数字xは、32 ビット 2 進浮動小数点が区別できる 10 進数の桁数をはるかに超えています。64 ビットで識別できる桁数を超えています。したがって、通常の算術演算を使用してこれらの変換を実行することはできません。なんらかの形式の拡張精度演算が必要です。

(実際、1.30000001192092895507812500000000…<i>x を考えてみましょう。xがその数字の任意の数のゼロの後にゼロ以外の数字である場合、変換は切り上げられる必要があります。ゼロ以外の数字がない場合、変換は丸められる必要があります。これは、正確に丸められた結果を決定するために調べなければならない桁数に制限がないことを意味します. 幸いなことに、紙に示されているように、桁をスキャンする以外に、実行しなければならない計算量には制限があります.)

于 2013-10-11T20:48:09.967 に答える
2

strtof/strtod のソースを見ると、double を使用してから float にキャストされます。

float を double に置き換えると、strtof と同じ結果が得られます。

#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <math.h>
int dec_to_f(char *dec, float *f)
{
int i = 0;
double tmp_f = 0;
if(dec == NULL) return 1;
if(f == NULL) return 2;
if(dec[i] == '\000') return 3;
if(dec[i] == '-')
{
    i++;
    if(dec[i] == '\000') return 3;
    for(; dec[i] != '\000'; i++)
    {
        if(dec[i] == '.')
        {
            double dec_place = 10;
            int power_of_ten = 1;
            for(i++; dec[i] != '\000'; i++, power_of_ten++, dec_place *= 10)
            {
                if(dec[i] >= '0' && dec[i] <= '9')
                {
                    if(power_of_ten > FLT_MAX_10_EXP) return 4;
                    else tmp_f -= (dec[i] - '0') / dec_place;
                }
                else return 5;
            }
            break;
        }
        if(dec[i] >= '0' && dec[i] <= '9')
        {
            tmp_f = tmp_f * 10 - (dec[i] - '0');
            if(!isfinite(tmp_f)) return 6;
        }
        else return 5;
    }
}
else
{
    if(dec[i] == '+')
    {
        if(dec[i+1] == '\000') return 3;
        else i++;
    }
    for(; dec[i] != '\000'; i++)
    {
        if(dec[i] == '.')
        {
            double dec_place = 10;
            int power_of_ten = 1;
            for(i++; dec[i] != '\000'; i++, power_of_ten++, dec_place *= 10)
            {
                if(dec[i] >= '0' && dec[i] <= '9')
                {
                    if(power_of_ten > FLT_MAX_10_EXP) return 7;
                    else tmp_f += (dec[i] - '0') / dec_place;
                }
                else return 5;
            }
            break;
        }
        if(dec[i] >= '0' && dec[i] <= '9')
        {   
            tmp_f = tmp_f * 10 + (dec[i] - '0');
            if(!isfinite(tmp_f)) return 8;
        }
        else return 5;
    }
}
*f = (float)tmp_f;
return 0;
    }
int main()
{
printf("FLT_MIN = %.38f\n", FLT_MIN);
printf("FLT_MAX = %f\n", FLT_MAX);
float f = 0;
int return_value = 0;
char str[256];
printf("INPUT = ");
scanf("%s", str);
return_value = dec_to_f(str, &f);
printf("return_value = %i\nstr = \"%s\"\nf = %.38f\nstrtof = %.38f\n", return_value, str, f, strtof(str, NULL));
}
于 2013-10-11T20:53:11.137 に答える
1

@Eric Postpischil と @Nahuel Fouilleul が良い情報を提供してくれました。コメントとしてうまく収まらないいくつかの考えを追加します。

1) FP へのテキストは、逆方向に評価する必要があります。最上位桁から最下位桁ではなく。最下位から最上位への結果を形成します。先行ゼロを無視します。これにより、テキストの最下位桁の微妙な効果が維持されます。右から左に移動するときは、最後に power_of_10 の倍数を維持します。

power_of_ten *= 10.0;
...
loop()
  // tmp_f = tmp_f * 10 + (dec[i] - '0');
  tmp_f = tmp_f/10 + (dec[i] - '0');
  power_of_ten *= 10.0;
...
tmp_f *= power_of_10;

2) DP.に気づいたら (右から左へ)、power_of_10 を 1.0 にリセットします。

-3)と+コードを 1 つに折ります。

4) "%.9e" を使用して結果を比較します。

5)next_afterf(x,0.99*x)およびを使用して、next_afterf(x,1.01*x)許容できる結果を囲みます。

6) 典型的なものfloatは、約 1 乗 (2,23) の精度 (10 進数で 7 桁まで) です。OPがそれに近づいているため、全体的な変換は問題ありません。逆の解析が必要です。

于 2013-10-11T22:45:43.973 に答える