如何正确测试浮点数或倍数以进行质量和其他比较

时间:2019-03-31 13:39:13

标签: c# wpf linq equality

比较存储为双精度数的实数时遇到一些问题。 我认为问题很可能是由舍入错误引起的,但我不确定。 比较在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以删除小数点并只比较整数? 那是个好主意吗?

2 个答案:

答案 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的支持有多么容易吗?或者,如果第三方信息发生更改,或者您需要更多(或更少)属性,更改多少?

几乎所有的代码都可以使用本地信息类进行测试。只需一个测试类即可测试第三方信息是否正确转换为您的信息。