比较存储为双精度数的实数时遇到一些问题。 我认为问题很可能是由舍入错误引起的,但我不确定。 比较在linq中存储为double并经过测试的数字的最佳方法是什么?
我从第三方来源获得了一个字符串时间。
看起来距离纪元已几秒钟
要将其转换为实时,请确保以秒为单位,而不是毫秒。
我用双重掩盖了
double Time = Convert.ToDouble(“ 1549666889.6220000”);
然后我使用linq从列表中提取这次包含的所有条目
Infos.Where(x => x.StartTime <= starttime
&& x.EndTime >= starttime).OrderBy(x => x.StartTime).ToList();
,我得到的结果似乎超出了我预期的比较范围。 我希望返回的项目是即时消息测试的时间在“信息”列表中项目的开始时间和结束时间之间。
我得到类似
(抱歉,下一批应该是开始时间和结束时间的表,但是我无法在此处以表格的格式进行格式化)
开始时间结束时间
1549665989.622097 1549666889.6221507
1549665989.6690228 1549666889.6790602
1549665989.8786857 1549666889.8817368
1549665989.8926628 1549666889.9037011
这些结果似乎是错误的,尤其是开始时间,因为它们应该小于给定的时间索引。
我认为这是一个四舍五入的问题,但不确定是那样还是我的逻辑。 如果这是一个四舍五入的问题,我应该如何在LINQ中进行测试。
任何建议表示赞赏。
在我看来,也许我应该将每个双精度值乘以10000000以删除小数点并只比较整数? 那是个好主意吗?
答案 0 :(得分:2)
将"1549665989.622097"
之类的字符串转换为双精度会导致错误。在这种情况下,转换后的double将为1549665989.6221
。
如果双精度错误是一个问题,则应使用decimal数据类型:
十进制关键字指示128位数据类型。与其他浮点类型相比,十进制类型具有更高的精度和更小的范围,使其适用于财务和货币计算。
Convert.ToDecimal从字符串提供所需的转换。结果将是1549665989.622097
,没有精度错误。
答案 1 :(得分:0)
您确实意识到您将Where
的StartTime字符串转换为双精度,而您的OrderBy
的很多次都不是吗:OrderBy会将第一个元素与第2个,第1个与第3个,第2个与第3个,第一个与第4个,第2个与第4个,第3个与第4个:将字符串一次又一次地转换为双倍。
记住这种转换并重新使用转换后的值会更有效吗?
无论如何我们都在转换第三方数据,为什么不将其转换为代表时间点的适当对象:System.DateTime
?
编写Info类的两个扩展功能:
static class InfoExtensions
{
public static DateTime StartDateTime(this Info info)
{
return info.startTime.ToDateTime();
}
public static DateTime EndDateTime(this Info info)
{
return info.endTime.ToDateTime();
}
private static DateTime ToDateTime(this string date3rdParty)
{
// ask from your 3rd party what the value means
// for instance: seconds since some start epoch time:
static DateTime epochTime = new DateTime(...)
double secondsSinceEpochTime = Double.Parse(date3rdParty);
return epochTime.AddSeconds(secondsSinceEpochTime);
}
}
用法:
DateTime startTime = ...
var result = Infos.Select(info => new
{
StartTime = info.StartTime.StartDatetime(),
EndTime = info.EndTime.EndDateTime(),
// select the Info properties you actually plan to use:
...
// or select the complete Info:
Info = info,
})
.Where(info => info.StartTime <= startTime && startTime <= info.EndTime)
.OrderBy(info => info.StartTime)
// Only if you prefer to throw away your converted StartTime / EndTime:
.Select(info => info.Info);
可能是您的第三方时间的精度与DateTime的精度不同,并且您需要最高的精度。在这种情况下,请考虑将其字符串转换为DateTime.Ticks
,然后使用此Ticks来创建新的DateTime对象。由于Ticks是整数,因此转换的麻烦将减少
您应该在separation of concerns上进行更多工作。如果将您的3rdparty表示他们的日期概念(某种时期以来以秒表示的字符串表示形式)与您希望的方式(可能是System.DateTime)分开,那么您就不会遇到这个问题。>
如果您将其info
类与info
类分开,则代码将具有更好的可维护性,因为只有一个地方可以将其info属性转换为info属性。如果将来他们添加了您不使用的属性,您将不会注意到它。如果他们决定更改日期想法,例如通过使用不同的纪元时间,或者使用System.DateTime,则只有一个地方需要更改信息。另外:如果有第四方信息:只有一个地方需要转换。
分离非常有效:无论您使用属性StartTime多么频繁,转换都只完成一次。例如,如果将来您想按同一日期将所有信息分组。
分离也更容易测试:大多数代码将与您自己转换的信息类一起使用。只有一小段代码会将其信息转换为您的信息概念。您可以使用info类测试大多数代码,而它们只是测试转换的一个地方:一旦知道转换可以,就不必再担心了
创建一个类MyNamespace.Info,该类具有构造函数thirdPartyNamespace.Info:
class MyInfo
{
public DateTime StartTime {get; set;}
public DateTime EndTime {get; set;}
... // other info properties you actually plan to use
// Constructors:
public MyInfo() { } // default constructor
public MyInfo(ThirdParyNameSpace.Info info)
{
this.StartTime = info.StartTime.ToDateTime();
this.EndTime = info.EndTime.ToDateTime();
...
}
}
您看到添加第四方对Info的支持有多么容易吗?或者,如果第三方信息发生更改,或者您需要更多(或更少)属性,更改多少?
几乎所有的代码都可以使用本地信息类进行测试。只需一个测试类即可测试第三方信息是否正确转换为您的信息。