【问题标题】:What is the best way to recognize and convert these "minimum" double values into C# Double.MinValue?识别这些“最小”双精度值并将其转换为 C# Double.MinValue 的最佳方法是什么?
【发布时间】:2013-06-03 15:26:02
【问题描述】:

我有一个需要转换为当前形式的数据库表。此表包含三列 Double 类型(如果有人关心,则为 Pervasive.SQL)。

我的问题是这个表已经存在了很长时间,并且它已经被代码追溯到大约 15 年或更长时间了。

从历史上看,我们一直使用Double.MinValue(或当时任何等效的语言)来表示用户提供的"blank" 值。换句话说,值的缺失实际上是存储为我们以后可以识别并做出智能反应的值。

所以,今天我的问题是我需要遍历这些记录并将它们插入到一个新创建的表中(这就是我所说的“转换”)。但是,我在要转换的表中没有看到一致的值。以下是我肯定知道的:

2.2250738585072014E-308
3.99285938963E-313
3.99099435427E-313
1.1125369292536007E-308
-5.389000690742776E279
2.104687961E-314

现在,我认识到Double.MinValue 可能存在或至少被表示的其他方式。做了一些谷歌搜索,我发现第一个是Double.MinValue 的另一种表示(实际上DBL_MIN 在这里引用:http://msdn.microsoft.com/en-us/library/6bs3y5ya(v=vs.100).aspx)。

我不想太啰嗦,所以如果这些信息不足以帮助我,我会提出问题。可以这么说,我需要一种可靠的方法来发现所有以前的“最小值”值,并在循环这些数据行时将它们替换为 C# Double.MinValue 常量。

如果证明是dataRow["Value"] < someConstant,那就这样吧。但我会让数学理论家帮我下定决心。

感谢您的宝贵时间。

编辑:

当我找到这些值时,这就是我正在做的事情。它是组装要写入数据库的值的通用方法的一部分:

else if (c.DataType == typeof(System.Double))
{
    if (inRow[c] == DBNull.Value)
        retString += @"NULL";
    else
    {
        Double d;
        if (Double.TryParse(inRow[c].ToString(), out d))
        retString += d.ToStringFull();
    }
}

直到现在,它只是简单地接受了它们。这很糟糕,因为当应用程序找到它们时,它们看起来像是可接受的数据,而 像 Double.MinValue。因此,不被视为空白。但他们就是这样。

【问题讨论】:

  • 如果您只是通过SELECT * From Table ORDER BY YourColumn 以升序查看数据会怎样。可能很容易发现存在巨大差距和真实数据开始的地方。
  • 我希望您将它们作为null 存储在新表中
  • 最小的“真实”值有多小?任何负值都是“真实的”吗? 0 是“真实的”吗?
  • @DonBoitnott:你应该听取这些人的建议,而不是无视它;你这里的这个系统被破坏得惊人。现在是修复它的时候了,而不是让它的破损更加一致。无效的双精度值应始终表示为数据库空值或双精度 NaN 值,从不为非常小的正数或非常大的负数。
  • @DonBoitnott 如果您提出问题,希望人们会质疑您的整个方法。所有健康的技术社区都因批评而兴旺发达。批评不是坏事。只有通过批评,我们才能提高。因此,您可以选择忽略建议,但不要指望我们会停止提供建议。

标签: c# floating-point double


【解决方案1】:

这太疯狂了。让我们详细看看其中的一些数字。这些都是略大于零的微小数字:

2.2250738585072014E-308

这是 1 / 21022 -- 这是一个普通的双精度数。这是您的集合中的两个“特殊”数字之一;它是大于零的最小正常双精度数。您列表中其余的小双打都是不正常的双打。

1.1125369292536007E-308

这是 1 / 21023 -- 它是次正规的双倍。这也是一个特殊的数字;它是一半 大于零的最小正常双精度。 (我最初说它是最大的次正规双,但当然这不是真的;请参阅 cmets。)

3.99285938963E-313

这没什么特别的。它是一个次正规双精度数,等于分子为 154145 且分母为 2 的相当大的幂的分数。

3.99099435427E-313

这也没什么特别的。这次的分子是 154073。

2.104687961E-314

这也没什么特别的。分子是 2129967929,分母是 2 的更大幂。

到目前为止,所有数字都非常接近于零且为正数。这个数字远非零和负数,因此很突出:

-5.389000690742776E279

不过这也没什么特别的;它甚至与绝对值最大的负双精度数相差甚远,约为-1.79E308,大约是十亿倍。

这是一团糟。

我的建议是立即停止这种疯狂行为绝对没有意义使用非常接近于零的值来表示“空白”值;非常接近零的值应四舍五入为零,而不是视为空白!

Double 已经有一个“空白”值的代表,即 Double.NaN -- Not A Number;当域已经包含特定的“无效”值时,使用 有效值 来表示 无效值 是很奇怪的。 (请记住,实际上存在大量不同的 NaN 位模式;使用 IsNaN 来确定 double 是否为 NaN。)

所以我的建议是:

  • 检查单独数据库中的每个非正态或非常小的正态双精度数。其中一些可能应该为零,并且由于舍入误差而最终成为微小的值。用零替换它们。应该为空的,替换为数据库空值(最佳实践)或双 NaN(可接受,但不如数据库空值。)

  • 编写一个程序,找出数据库中每个绝对值不可能大的数字,并将其替换为数据库 null 或 double NaN。

  • 更新所有客户端,以便他们了解您用来表示空白值的约定。

【讨论】:

  • 2**–1023 不是最大的次正规。它是最大的次正规,只有一位设置。最大的次正规是 2**–1022 – 2**–1074。 (不过还是很疯狂。)
  • @EricPostpischil:哎呀,你说得完全正确。我的错。我会更正文字。
  • 最后,我接受了这个答案,因为我不同意所有人所说的关于我工作的糟糕状态的说法。事实上,我怀疑即使@tmyklebu 也同意,尽管尝试回答我的直接问题。感谢所有回答的人,我们随时欢迎反馈。并感谢 Eric 对价值观的解释以及(惩罚)建议。 :)
  • 要使减法的结果不为零但非常小,您必须减去两个非常小的东西。
  • @tmyklebu:啊,是的。你对“非常小”的定义和我的不同! :-) 我收回我的反对意见。
【解决方案2】:

您似乎想检查 double 是否真的很小且为正数或真的很大、有限且为负数。 (其他人已经详细说明了您在 cmets 中的方法存在的一些问题;我不打算在这里讨论。)像这样的测试:

if (d == d && (d > 0 && d < 1e-290 || d < -1e270 && d + d != d))

大概可以做你想做的事。您可能需要调整上面的数字。 d == d 测试正在检查 NaN,而 d + d != d 测试正在检查无穷大。

【讨论】:

  • 虽然d==d 是对 NaN 的廉价测试,但最好调用 Double.IsNaN;这样,未来不理解 NaN 的代码读者将被引导阅读文档,而不是消除看似不必要的比较。
  • double.IsNegativeInfinitydouble.IsPositiveInfinity 也是如此——让代码易于阅读,而不是使用神秘的技巧。
  • 除了 Eric 的 cmets,他拥有的数字不是 NaN,也不是无穷大,所以这对他没有帮助。
  • @EricLippert:酷。如果我必须编写 C# 代码,我会记住这一点。
  • @Servy:我认为您误解了代码。对于“有点像”OP发布的有限数字,测试成功。 NaN 和无穷大的测试用于处理非有限数的双精度数。
猜你喜欢
  • 1970-01-01
  • 2019-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-20
  • 1970-01-01
  • 2011-03-11
相关资源
最近更新 更多