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

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

在过去的几个月里,我一直在使用C#进行ECMAScript实现,而且我一直在减慢填充引擎中的漏洞。 昨晚我开始研究Number.prototype.toString ,它在ECMAScript规范 (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  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  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  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#会更高效。

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()转换。仔细阅读它们,看看它有多复杂正确舍入转换。)

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

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

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

看看dtoa.c实现顶部评论中引用的论文,克林格如何准确地读取浮点数 , 以了解问题的风格; 也许David M. Gay(作者)的论文, 正确的圆二进制 – 十进制和十进制 – 二进制转换 。

(另外,更一般地说: 每个计算机科学家应该知道的关于浮点运算的内容 。)

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

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

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

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