是否有任何 C# 规范说明如何隐式转换整型类型的项(例如,int
) 的条款double
应该可以工作吗?如果是这样,有人可以告诉我算法或指导我吗?
C# 6.0 规范草案 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure在词法结构 -> 语法 -> 词法语法 -> 词法分析 -> 标记 -> 文字 -> 实数下指出“float 或 double 类型的实数文字的值是通过使用 IEEE‘舍入到最近’模式来确定的” ;但是我无法找到有关隐式转换如何工作的任何信息。
我在同一规范中的“转换”->“隐式转换”->“隐式数字转换”下发现的唯一内容是“从int
, uint
, long
, or ulong
to float
和来自long
or ulong
to double
可能会导致精度损失,但永远不会导致幅度损失。”
I do know that implicit conversions don’t follow the same algorithm that real literals do as the below program illustrates*:
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];
}
}
}
Edit
上面的代码可能比它需要的更“复杂”。这是另一个程序,希望能够澄清我的问题。
using System;
using System.Diagnostics;
namespace App
{
internal static class Program
{
internal static void Main()
{
Debug.Assert(10648738977740919977 != 10648738977740919977d);
}
}
}
Addendum
正如 The General 和 mjwills 在评论中所述,这几乎肯定是由于 x86 等某些 ISA 提供的扩展精度格式造成的。至于为什么.NET Core编译器依赖扩展格式来转换ulong
to a double
但对于真正的文字却没有做同样的事情超出了我的范围。不确定这在技术上是否是一个“错误”,但如果两者都做了同样的事情那就太好了。由于 IEEE 754-2019 明确允许超过 64 位的精度,因此可以符合上述规范并仍然使用扩展格式。无论如何,ulong
value 可以完全适合 x86 扩展格式的 64 位有效数,因此不会进行舍入。
TL;DR(又名编辑 2)
我将以这样一个事实作为序言:我从根本上和哲学上反对我将要写的内容是必要的甚至是可取的这一观念。我相信像这样的特定编程语言特有的技术问题仍然“适合”Stack Overflow,而不是任何其他 Stack Exchange 网站(例如,计算机科学、理论计算机科学、数学和 Math Overflow,例如同伦之类的问题)类型理论)。这意味着想要了解某件事的实质细节——即使人们可能(错误地)认为这些事情会导致违反“最佳实践”——仍然是一个值得提出的问题。如果存在更根本的问题,那么可以提出一个单独的问题。
背景
我正在创建一个 128 位无符号整数类型,U128
,在我的工作中,我们使用 VB.NET 进行编写。我决定实现显式强制转换的功能U128
条款Double
(i.e., double
用 C# 术语来说)。 IEEE 754 二进制 64 和二进制 32 是相当简单的格式,因为它们几乎与以 10 为基数的实数的格式相同 - 当然它们必须制成有限的位序列并具有有偏差的指数。不管怎样,我首先在 Rust 中实现了它,因为 Rust 有一个原生的 128 位无符号整数类型,u128
; Rustonomicon 明确说明了如何从u128
条款f64
条款行为。这让我可以用 Rust 的算法来测试我的算法;毫不奇怪,由于该算法的琐碎性质(大约 12 行代码),我的实现在几个边缘情况和 10 亿个随机生成的数字上与 Rust 的实现相匹配 - 不,我没有花时间正式验证我的算法是否正确。
然后,我将我的算法移植到 VB.NET,因为我知道 C# 在这里更受欢迎,所以我也用 C# 重写了它,并确认它具有相同的行为,但我想确信在翻译过程中不会丢失任何内容。我能做的最好的事情就是比较演员阵容ULong
(ulong
在 C# 中)术语Double
具有等价强制转换的术语ULong
s as U128
s to Double
s。果然,当我发现时我很沮丧10648738977740919977UL
与同等产品的铸造方式不同U128
。我(正确地)假设舍入存在问题 — 仅供参考,C# 规范没有说明如何对完全位于两个数字之间的数字进行舍入;但正如预期的那样,它四舍五入到偶数。当我比较第一个字节时(我使用的是小端 CPU)Double
当我的演员阵容与 Rust 的演员阵容创建时,我发现我的演员阵容是正确的。此时,我认为 VB.NET 有一些“可疑”的地方(后来在 C# 中得到了证实),因为我通常更信任 Rust,并且如前所述,该算法相当简单。
幸运的是,我当时not意识到 C# 允许程序在具有扩展精度功能的 CPU 上使用扩展精度功能的(不幸的)怪癖包括不合规的 CPU,例如仅具有 80 位精度的基于 x86 的 CPU。如果我知道这一点,我可能会放弃它。
直到我检查了第一个字节Double
term, 10648738977740919977R
(10648738977740919977d
在 C# 中),我真的很困惑,因为我发现它did同意我的算法。怎么会这样?我使用了相同的机器,使用相同的编译器针对相同的平台进行编译。最后,我正确地推测词法解析器处理真实文字的方式与处理转换为整数文字的方式可能存在行为差异Double
s。为了测试这个理论,我在最初的帖子中修改了程序(当时使用的是 VB.NET)。
此时,我假设隐式转换使用了一种不同的算法(可能是出于效率原因,因为必须跟踪 3 个附加位才能知道如何正确舍入)。That这就是为什么我的问题是这样提出的。我想知道算法这样我的算法就会与它保持一致(即使我的初始算法(很可能)在技术上按照 IEEE 754 是正确的)。
幸运的是,在 mjwills、The General 和 NetMage 最终的帮助下,我们发现这很可能是由于我的 CPU 的扩展精度功能不合规所致;尽管事实上这发生在汇编时间与之前强调的帖子根本不同runtime差异。
我鼓励大家花时间阅读amazingtannergooding 在我最终发布的答案的链接中给出了答案和评论(包括花费 15 美元阅读有关何时可以使用扩展精度能力及其要求的正式证明)。
* Compiled with Microsoft Visual C# Compiler version 3.7.0-6.20459 for .NET Core 3.1 on Windows 10 Pro 18363.1139 on an Intel Core i7-6600U CPU.