21

低レベルのプログラミングに関する私の全体的な知識が少しまばらであることを最初に認めます。コアとなる概念の多くは理解していますが、定期的に使用することはありません。そうは言っても、 dtoa.cに必要なコードの量には本当に驚きました。

過去 2 か月間、私は C# での ECMAScript の実装に取り​​組んできましたが、エンジンの穴を埋めるのに時間がかかりました。昨夜、ECMAScript 仕様(pdf)のセクション15.7.4.2で説明されているNumber.prototype.toStringの作業を開始しました。セクション9.8.1で、注 3 はdtoa.cへのリンクを提供していますが、私はチャレンジを探していたので、表示するのを待ちました。以下は私が思いついたものです。

private IDynamic ToString(Engine engine, Args args)
{
    var thisBinding = engine.Context.ThisBinding;
    if (!(thisBinding is NumberObject) && !(thisBinding is NumberPrimitive))
    {
        throw RuntimeError.TypeError("The current 'this' must be a number or a number object.");
    }

    var num = thisBinding.ToNumberPrimitive();

    if (double.IsNaN(num))
    {
        return new StringPrimitive("NaN");
    }
    else if (double.IsPositiveInfinity(num))
    {
        return new StringPrimitive("Infinity");
    }
    else if (double.IsNegativeInfinity(num))
    {
        return new StringPrimitive("-Infinity");
    }

    var radix = !args[0].IsUndefined ? args[0].ToNumberPrimitive().Value : 10D;

    if (radix < 2D || radix > 36D)
    {
        throw RuntimeError.RangeError("The parameter [radix] must be between 2 and 36.");
    }
    else if (radix == 10D)
    {
        return num.ToStringPrimitive();
    }

    var sb = new StringBuilder();
    var isNegative = false;

    if (num < 0D)
    {
        isNegative = true;
        num = -num;
    }

    var integralPart = Math.Truncate(num);
    var decimalPart = (double)((decimal)num.Value - (decimal)integralPart);
    var radixChars = RadixMap.GetArray((int)radix);

    if (integralPart == 0D)
    {
        sb.Append('0');
    }
    else
    {
        var integralTemp = integralPart;
        while (integralTemp > 0)
        {
            sb.Append(radixChars[(int)(integralTemp % radix)]);
            integralTemp = Math.Truncate(integralTemp / radix);
        }
    }

    var count = sb.Length - 1;
    for (int i = 0; i < count; i++)
    {
        var k = count - i;
        var swap = sb[i];
        sb[i] = sb[k];
        sb[k] = swap;
    }

    if (isNegative)
    {
        sb.Insert(0, '-');
    }

    if (decimalPart == 0D)
    {
        return new StringPrimitive(sb.ToString());
    }

    var runningValue = 0D;
    var decimalIndex = 1D;
    var decimalTemp = decimalPart;

    sb.Append('.');
    while (decimalIndex < 100 && decimalPart - runningValue > 1.0e-50)
    {
        var result = decimalTemp * radix;
        var integralResult = Math.Truncate(result);
        runningValue += integralResult / Math.Pow(radix, decimalIndex++);
        decimalTemp = result - integralResult;
        sb.Append(radixChars[(int)integralResult]);
    }

    return new StringPrimitive(sb.ToString());
}

低レベルのプログラミングの経験が豊富な人なら、dtoa.cのコードが約 40 倍になる理由を説明できますか? C# がそれほど生産的であるとは想像できません。

4

5 に答える 5

43

dtoa.c には、double を文字列に変換する dtoa() と、文字列を double に変換する strtod() という 2 つの主要な関数が含まれています。また、多くのサポート関数も含まれており、そのほとんどは任意精度の算術演算を独自に実装するためのものです。dtoa.c の名声は、これらの変換を正しく行うことであり、それは一般に、任意精度の演算でのみ行うことができます。また、4 つの異なる丸めモードで変換を正しく丸めるコードもあります。

あなたのコードは dtoa() に相当するものを実装しようとするだけであり、浮動小数点を使用して変換を行うため、常に正しく変換されるとは限りません。(更新: 詳細については、私の記事http://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/を参照してください。)

(これについては、私のブログhttp://www.exploringbinary.com/で多くのことを書いてきました。最近の 7 回の記事のうち 6 回は、strtod() 変換だけに関するものでした。それらを読んで、変換がいかに複雑であるかを理解してください。正しく四捨五入された変換)。

于 2010-07-04T01:33:47.830 に答える
11

10 進浮動小数点表現と 2 進浮動小数点表現の間の変換で適切な結果を出すことは、かなり難しい問題です

困難の主な原因は、多くの小数は、たとえ単純なものであっても、2 進浮動小数点を使用して正確に0.5表現でき0.1ないことです。また、逆方向 (2 進数から 10 進数) では、絶対に正確な結果は必要ありません (たとえば、0.1IEEE-754 準拠の数値で表現できる最も近い数値の正確な 10 進数値は、double実際には です 0.1000000000000000055511151231257827021181583404541015625) 。したがって、通常は丸めが必要です。

そのため、変換には概算が含まれることがよくあります。優れた変換ルーチンは、特定の (ワード サイズまたは桁数) 制約内で可能な限り最も近い近似値を生成することを保証します。これは、複雑さのほとんどがどこから来るかです。

問題の特徴については、実装の上部にあるコメントで引用されているdtoa.cClinger のHow to Read Floating Point Numbers Accurately を参照してください。そしておそらく David M. Gay (著者) の論文、Correctly Rounded Binary-Decimal and Decimal-Binary Conversions .

(また、より一般的には、「すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと」 )

于 2010-07-03T23:35:00.927 に答える
4

また、dtoa.cのコードは(言語に関係なく)より効率的かもしれないと思います。たとえば、それは少しいじくり回しているように見えますが、それは専門家の手にはしばしばスピードを意味します。速度の理由から、直感的ではないアルゴリズムを使用していると思います。

于 2010-07-03T23:10:46.777 に答える
4

ざっと見てみると、かなりの量の C バージョンが複数のプラットフォームを扱っており、このファイルは、コンパイラ (C & C++)、ビット数、浮動小数点の実装、およびプラットフォーム間で一般的に使用できるように意図されているように見えます。 ; たくさんの#define設定可能性があります。

于 2010-07-03T22:41:25.030 に答える
3

短い答え:dtoa.cうまくいくからです。

これはまさに、十分にデバッグされた製品と NIH プロトタイプの違いです。

于 2010-07-04T19:21:52.173 に答える