首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >精确的双精度,通过正确的舍入

问精确的双精度,通过正确的舍入
EN

Stack Overflow用户
提问于 2017-01-25 16:30:24
回答 2查看 348关注 0票数 1

虽然我的问题听起来微不足道,但实际上并非如此。希望你能帮助我。

我想在我的.NET (C#)项目中实现区间算术。这意味着每个数字都由一个下界和一个上界定义。这对解决像这样的问题很有帮助

代码语言:javascript
复制
1 / 3 = 0.333333333333333 (15 significant digits)

因为这样你就会有

代码语言:javascript
复制
1 / 3 = [ 0.33333333333333 , 0.333333333333334 ] (14 significant digits each)

,所以我现在确信正确的答案就在这两个数字之间。如果没有区间表示,我就已经有了舍入误差(即0.0000000000000003)。

向下舍入结果将给出区间的下限,向上舍入结果将给出区间的上限。

.NET具有Math.Round(double,int)方法,该方法将double舍入到int个小数位。看起来不错,但不能强制向上/向下舍入。Math.Round(1.0/3.0,14)会向下舍入,但也需要向上舍入到0.33...34不能像这样实现。

但是你可能会说有Math.Ceil和Math.Floor!好的,这些方法舍入到下一个较低或较高的整数。因此,如果我想四舍五入到小数点后14位,我首先需要修改我的结果:

代码语言:javascript
复制
1 / 3 = 0.333333333333333 -> *E14 -> 33333333333333.3

因此,现在我可以调用Math.Ceil和Math.Floor,并在重新调整后获得这两个四舍五入的结果

代码语言:javascript
复制
33333333333333 & 33333333333334 -> /E14 -> 0.33333333333333 & 0.33333333333334

看起来不错,但是:假设我的数字接近double.MaxValue。我不能只是在double.MaxValue附近*E14一个值,因为这会给我一个OverflowException。因此,这也不是解决方案。

最重要的是:当尝试对0.9999999999999999999999999 (more than 15 digits)进行舍入时,所有这些都会失败,因为在我可以开始向下舍入之前,内部表示已经舍入为1。

我可以尝试以某种方式解析包含双精度值的字符串,但这不会有帮助,因为(1/3 * 3).ToString()将打印1而不是0.99...9。

Decimal也不能工作,因为我不想要那么深的精度,14位就足够了;但我仍然想要那个双倍范围!

在存在多个区间算术实现的C++中,这个问题可以通过动态地告诉处理器将其循环模式切换为例如"always down“或"always up”来解决。我在.NET中找不到任何方法来做这件事。

那么,你有什么想法吗?提前感谢!

EN

回答 2

Stack Overflow用户

发布于 2017-01-26 00:08:57

假设nextDown(x)是一个返回小于x的最大双精度数的函数,nextUp(x)是一个返回大于x的最小双精度数的函数。有关实现思想,请参阅Get next smallest Double number。

其中,您将向下舍入下限结果,而不是使用舍入到最近的结果的nextDown。如果要向上舍入一个上界,请使用舍入到最近的结果的nextUp。

此方法可确保间隔继续包含精确的实数结果。它引入了额外的舍入误差-在某些情况下,下限将比它应该的值小一个ULP,和/或上限将比它大一个ULP。但是,这是对间隔的最小加宽,比在十进制或通过抑制低有效位得到的加宽要小得多。

票数 1
EN

Stack Overflow用户

发布于 2017-01-25 21:15:39

这可能更像是一条长长的评论,而不是一个真正的答案。

这段代码通过截断7个最低有效位返回一个" Interval“(我只使用Tuple<,>,您可以使用自己的Interval类型):

代码语言:javascript
复制
static Tuple<double, double> GetMinMaxIntervalBasedOnBinaryNumbersThatAreRoundOnLastSevenBits(double number)
{
  if (double.IsInfinity(number) || double.IsNaN(number))
    return Tuple.Create(number, number); // maybe treat this case differently

  var i = BitConverter.DoubleToInt64Bits(number);

  const int numberOfBitsToClear = 7; // your seven, can change this value, must be below 52
  const long precision = 1L << numberOfBitsToClear;
  const long bitMask = ~(precision - 1L);

  //truncate i
  i &= bitMask;

  return Tuple.Create(BitConverter.Int64BitsToDouble(i), BitConverter.Int64BitsToDouble(i + precision));
}

免责声明:我不确定这对任何目的是否有用。特别是不确定它对于区间算术是否有用。

使用此代码,GetMinMaxIntervalBasedOnBinaryNumbersThatAreRoundOnLastSevenBits(1.0 / 3.0)将返回元组(0.333333333333329, 0.333333333333336)。

这段代码,就像您在问题中询问的代码一样,有一个明显的“问题”:如果原始值接近(甚至等于)我们使用的一个“四舍五入”数字,那么返回的区间是“倾斜的”,原始数字接近区间的一个末端。例如,在输入42.0 (已经是圆形)的情况下,您可以得到元组(42, 42.0000000000009)。

这段代码的一个好处是我希望它非常快。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41846690

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档