为什么“dtoa.c”包含这么多代码?

时间:2010-07-03 22:30:32

标签: c# javascript c floating-point ecma262

我将是第一个承认我对低级编程的整体知识有点稀疏的人。我理解许多核心概念,但我不会定期使用它们。 话虽如此,我对dtoa.c需要多少代码感到非常震惊。

在过去的几个月里,我一直在使用C#进行ECMAScript实现,而且我一直在减慢填充引擎中的漏洞。昨晚我开始研究 Number.prototype.toString ECMAScript specification (pdf) 15.7.4.2 部分对此进行了描述。在 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#会更有效率。

5 个答案:

答案 0 :(得分:39)

dtoa.c包含两个主要功能:dtoa(),它将double转换为字符串,strtod()将字符串转换为double。它还包含许多支持函数,其中大部分都是为自己实现的任意精度算术。 dtoa.c声名鹊起就是让这些转换正确,而这通常只能通过任意精度算术来完成。它还具有在四种不同的舍入模式下正确舍入转换的代码。

您的代码只尝试实现dtoa()的等价物,并且因为它使用浮点来进行转换,所以并不总是正确。 (更新:有关详细信息,请参阅我的文章http://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/。)

(我在我的博客http://www.exploringbinary.com/上写了很多关于此内容的文章。我的最后七篇文章中有六篇仅关于strtod()转换。仔细阅读它们,看看正确舍入是多么复杂转换。)

答案 1 :(得分:8)

为十进制和二进制浮点表示之间的转换生成良好的结果是一个相当困难的问题。

困难的主要原因是许多小数部分,即使是简单的小数,也不能使用二进制浮点准确表示 - 例如,0.5可以(显然),但{ {1}}不能。而且,从另一个方向(从二进制到十进制),您通常不希望得到绝对准确的结果(例如,最接近0.1的数字的精确十进制值,可以用IEEE-754表示实际上是0.1 double)所以你通常想要一些舍入。

因此,转换通常涉及近似。良好的转换例程保证在特定(字大小或数字位数)约束内产生最接近的可能的近似值。这就是大多数复杂性来自的地方。

查看0.1000000000000000055511151231257827021181583404541015625实施顶部评论中引用的论文,Clinger的How to Read Floating Point Numbers Accurately,了解问题的风格;也许David M. Gay(作者)的论文Correctly Rounded Binary-Decimal and Decimal-Binary Conversions

(另外,更一般地说:What Every Computer Scientist Should Know About Floating Point Arithmetic。)

答案 2 :(得分:4)

基于对它的快速浏览,相当数量的C版本正在处理多个平台,例如看起来这个文件通常可用于编译器(C&amp; C ++),位,浮点实现和平台;大量#define可配置性。

答案 3 :(得分:4)

我认为dtoa.c中的代码可能更有效(独立于语言)。例如,它似乎做了一些小小的摆弄,在专家手中通常意味着速度。我认为出于速度原因,它只使用不太直观的算法。

答案 4 :(得分:2)

简短回答:因为dtoa.c有效。

这正是经过良好调试的产品和NIH原型之间的区别。