c# - 通过 double 往返 DateTime 而不会损失精度
问题描述
这个问题不是关于将 DateTime 序列化为 double 和 back 是否明智,而是关于当这是你必须做的事情时该怎么做。
表面上的解决方案是使用DateTime.ToOADate()
, 根据Convert DateTime to Double但这会严重损失精度,例如
let now = DateTime.UtcNow in DateTime.FromOADate(now.ToOADate()).Ticks - now.Ticks
结果类似于
val it : int64 = -7307L
,这非常糟糕,因为这几乎是一毫秒。
在这方面,一种更粗略的方法(只是在和之间进行转换long
(在 F#double
中调用float
)实际上要好一些:
let now = DateTime.UtcNow in DateTime(int64(float(now.Ticks))).Ticks - now.Ticks
结果类似于val it : int64 = -42L
-- 更好,但仍然不准确。例如,在C#: Double to long conversion中讨论了精度损失的原因。
所以问题是:有没有办法在不损失精度的情况下将 a往返DateTime
于 a之间?double
更新:接受的答案很清楚地解释了“它实际上是如何工作的”,但事实证明,System.BitConverter.Int64BitsToDouble()
并且System.BitConverter.DoubleToInt64Bits()
或多或少地做到了这一点,尽管显然受限于long
<->double
转换,并且仅限于小端机器。有关实际代码,请参阅https://referencesource.microsoft.com/#mscorlib/system/bitconverter.cs,db20ea77a561c0ac。
解决方案
由于您似乎并不关心生成的 double 或“hacky”方法的实际内容,而只关心将它们转换回来的能力并且两种类型都是unmanaged
,因此您可以使用一种非常通用的方法。
如果您启用不安全代码,您可以使用以下方法进行直接超快速实现stackalloc
:
static void Main(string[] args)
{
Check(nameof(DateTime.MinValue), DateTime.MinValue);
Check(nameof(DateTime.MaxValue), DateTime.MaxValue);
Check(nameof(DateTime.Now), DateTime.Now);
Check(nameof(DateTime.UtcNow), DateTime.UtcNow);
Console.ReadLine();
}
static void Check(string name, DateTime @DateTime)
{
Console.WriteLine($@"{name} expected: {@DateTime}");
var @double = ConvertUnmanaged<DateTime, double>(@DateTime);
@DateTime = ConvertUnmanaged<double, DateTime>(@double);
Console.WriteLine($@"{name} unmanaged returned: {@DateTime}");
@double = ConvertFixed<DateTime, double>(@DateTime);
@DateTime = ConvertFixed<double, DateTime>(@double);
Console.WriteLine($@"{name} address returned: {@DateTime}");
}
// types can be of different size
static unsafe TOut ConvertUnmanaged<TIn, TOut>(TIn pIn)
where TIn : unmanaged
where TOut : unmanaged
{
var mem = stackalloc byte[Math.Max(sizeof(TIn), sizeof(TOut))];
var mIn = (TIn*)mem;
*mIn = pIn;
return *(TOut*)mIn;
}
// types should be of same size
static unsafe TOut ConvertFixed<TIn, TOut>(TIn pIn)
where TIn : unmanaged
where TOut : unmanaged
{
if (sizeof(TIn) != sizeof(TOut)) throw new ArgumentException();
return *(TOut*)(&pIn);
}
这将输出:
MinValue expected: 01.01.0001 00:00:00
MinValue unmanaged returned: 01.01.0001 00:00:00
MinValue address returned: 01.01.0001 00:00:00
MaxValue expected: 31.12.9999 23:59:59
MaxValue unmanaged returned: 31.12.9999 23:59:59
MaxValue address returned: 31.12.9999 23:59:59
Now expected: 09.11.2020 16:43:24
Now unmanaged returned: 09.11.2020 16:43:24
Now address returned: 09.11.2020 16:43:24
UtcNow expected: 09.11.2020 15:43:24
UtcNow unmanaged returned: 09.11.2020 15:43:24
UtcNow address returned: 09.11.2020 15:43:24
如您所见,ConvertUnmanaged
将简单地转换任何unmanaged
类型,但临时持有类型(在您的情况下为双倍)大小应该与主要类型的大小(在您的情况下为 DateTime)具有相同或更大的大小
ConvertFixed 有点受限
推荐阅读
- python - 无法使用 Pip 安装更新 Python 库
- android - 领域 SyncConfiguration.Builder 已弃用
- reactjs - 在 FormDecortor 字段中按 Enter 键时停止在 React Antd Design 中提交表单
- amazon-web-services - AMI 可用时通知
- python - 如何通过 Django REST 框架序列化列表
- angular - 如何动态绑定 Angular 2 下拉菜单
- cassandra - 在 Kubernetes 上备份和恢复 Cassandra
- java - 页
对象在 foreach 循环中抛出 NPE - c# - 如何在 Docx4j.net 中禁用 Plutext?
- javascript - 执行多个 AJAX 请求的正确方法是什么,每个请求都在 setInterval() 中?