是否有任何 C# 规范说明了整数类型项(例如,int)到项的隐式转换double应该如何工作?如果是这样,有人可以告诉我算法或指导我吗?
C# 6.0 草案规范在词法结构 -> 语法 -> 词法语法 -> 词法分析 -> 令牌 -> 文字下声明“float 或 double 类型的实数的值是通过使用 IEEE '舍入到最近'模式确定的” -> 真正的文字;但是我无法找到有关隐式转换如何工作的任何信息。
我在同一规范中的转换 -> 隐式转换 -> 隐式数字转换下找到的唯一内容是“从int, uint, long, 或ulongtofloat和 fromlong或ulongto 的转换double可能会导致精度损失,但永远不会导致幅度损失。”
我确实知道隐式转换不遵循与真实文字相同的算法,如下面的程序所示*:
using System;
using System.Diagnostics;
namespace App
{
internal static class Program
{
internal static void Main()
{
Debug.Assert(GetFirstByte(10648738977740919977) != GetFirstByte(10648738977740919977d));
}
private static byte GetFirstByte(double val)
{
return BitConverter.GetBytes(val)[0];
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑
上面的代码可能比它需要的更“复杂”。这是另一个程序,希望能澄清我在问什么。
using System;
using System.Diagnostics;
namespace App
{
internal static class Program
{
internal static void Main()
{
Debug.Assert(10648738977740919977 != 10648738977740919977d);
}
}
}
Run Code Online (Sandbox Code Playgroud)
附录
正如 The General 和 mjwills 在评论中所述,这几乎可以肯定是由于 x86 等一些 ISA 提供的扩展精度格式。至于为什么 .NET Core 编译器依赖扩展格式将 the 转换ulong为 adouble但对真正的文字不做同样的事情,我无法理解。不确定这在技术上是否是一个“错误”,但如果两者都做同样的事情会很好。可以符合上述规范并仍然使用扩展格式,因为 IEEE 754-2019 明确允许超过 64 位的精度。无论如何,该ulong值可以完全适合 x86 扩展格式的 64 位有效数,从而导致没有舍入。
TL;DR(又名编辑 2)
我将以这样一个事实作为本次编辑的序言,即我从根本上和哲学上反对我将要写的内容是必要的,甚至是可取的。我相信特定于特定编程语言的技术问题仍然“适合” Stack Overflow 而不是任何其他 Stack Exchange 站点(例如,计算机科学、理论计算机科学、数学和数学溢出——对于诸如同伦之类的东西)类型理论)。这意味着想要了解某事的本质细节——即使人们可能(错误地)认为这些事情会导致违反“最佳实践”——仍然是一个有价值的问题。如果存在更基本的问题,则可以针对它提出一个单独的问题。
背景
我正在创建一个 128 位无符号整数类型U128,在我用 VB.NET 编写的工作中。我决定实现将U128术语显式转换为 Double(即,double在 C# 中)术语的能力。IEEE 754 binary64 和 binary32 是相当简单的格式,因为它们几乎与基数为 10 的实数的格式相同——当然,它们必须被制成有限的位序列并具有偏置指数。无论如何,我第一个实现它在锈病自锈有一个本机128位无符号整数类型,u128; 和 Rustonomicon 明确说明如何从u128条款转换为f64条款行为。这让我可以用 Rust 测试我的算法;不出所料,由于算法的微不足道的性质——它是?12 行代码——我的实现与 Rust 的几个边缘情况和 10 亿个随机生成的数字相匹配——不,我没有花时间正式验证我的算法是否正确。
然后我将我的算法移植到 VB.NET——知道 C# 在这里有多流行,我也用 C# 重写它并确认它具有相同的行为——但我想确信在翻译中没有丢失任何东西。我能做的最好的事情是将ULong(ulong在 C# 中)术语的Double转换与具有等效ULongs转换为U128s 的术语进行比较Double。果然,当我发现10648738977740919977UL被施放的与同等的人不同时,我感到很沮丧U128。我(正确地)假设舍入有问题——仅供参考,C# 规范没有说明如何舍入完全位于两个数字之间的数字;但正如预期的那样,它四舍五入到偶数。当我比较第一个字节时——我使用的是小端 CPU——Double我的演员是用 Rust 的演员创造的,我发现我的演员是正确的。在这一点上,我认为 VB.NET 有一些“可疑”(后来在 C# 中得到证实),因为我通常更信任 Rust,而且如前所述,该算法相当简单。
幸运的是,我没有意识到 C# 允许程序在 CPU 上使用扩展精度功能的(不幸的)怪癖,包括不兼容的 CPU,例如只有 80 位精度的基于 x86 的 CPU。如果我知道这一点,我可能会放弃它。
直到我检查了该Double术语的第一个字节10648738977740919977R(10648738977740919977d在 C# 中),我才真正感到困惑,因为我发现它确实与我的算法一致。这怎么可能?我使用了相同的机器,使用相同的编译器为相同的平台编译。最后,我正确地推测,词法解析器处理实际文字的方式与处理强制转换为Doubles 的整型文字的方式可能存在差异。为了测试这个理论,我在最初的帖子中修改了程序(当时在 VB.NET 中)。
在这一点上,我假设隐式转换使用了不同的算法(可能是出于效率原因,因为必须跟踪 3 个额外的位才能知道如何正确舍入)。这就是为什么我的问题是这样表述的。我想知道算法,以便我的算法与它保持一致(即使我的初始算法(很可能)按照 IEEE 754 在技术上是正确的)。
幸运的是,最终在 mjwills、The General 和 NetMage 的帮助下,发现它可能与我的 CPU 不兼容的扩展精度能力有关;尽管这发生在编译时这一事实与之前强调运行时差异的帖子有着根本的不同。
我鼓励每个人花时间阅读我最终发布的答案链接中 tannergooding的惊人答案和评论(包括支付 15 美元以阅读有关扩展精度能力何时可以使用以及此类要求的正式证明)。
*使用 Microsoft Visual C# 编译器版本 3.7.0-6.20459 编译,适用于英特尔酷睿 i7-6600U CPU 上的 Windows 10 Pro 18363.1139 上的 .NET Core 3.1。