正确的舍入

时间:2017-01-25 08:30:24

标签: c# floating-point rounding intervals

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

我想在.NET(C#)项目中实现区间运算。这意味着每个数字都由下限和上限定义。这对于像

这样的问题很有帮助
1 / 3 = 0.333333333333333 (15 significant digits)

因为你会有

1 / 3 = [ 0.33333333333333 , 0.333333333333334 ] (14 significant digits each)

,所以我现在确定正确的答案介于这两个数字之间。如果没有区间表示,我就会出现舍入错误(即0.0000000000000003)。

为了达到这个目的,我编写了自己的Interval类型,它重载了所有标准运算符,如+ - * /等。要使此类型正常工作,我需要能够舍入1 / 3的结果在两个方向。向下舍入结果将给出我的间隔的下限,将结果四舍五入将给出我的间隔的上限。

.NET具有Math.Round(double,int)方法,该方法将doubleint小数点四舍五入。看起来很棒,但不能强迫它向上/向下舍入。 Math.Round(1.0/3.0,14)会向下舍入,但也需要向上舍入到0.33 ...... 34才能达到这样的目标。

但您可能会说Math.CeilMath.Floor!好的,这些方法会转到下一个较低或较大的整数。因此,如果我想要舍入到14位小数,我首先需要改变我的结果:

1 / 3 = 0.333333333333333 -> *E14 -> 33333333333333.3

所以现在我可以调用Math.Ceil和Math.Floor并在改革后获得圆润的结果

33333333333333 & 33333333333334 -> /E14 -> 0.33333333333333 & 0.33333333333334

看起来很棒,但是:假设我的号码接近double.MaxValue。我不能*E14 double.MaxValue附近的值,因为这会给我一个OverflowException。所以这也不是解决办法。

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

我可以尝试以某种方式解析包含double的字符串,但这无济于事,因为(1/3 * 3).ToString()已经打印1而不是0.99 ... 9。

Decimal也不起作用,因为我不想要那么深的精度,14位就足够了;但我还是想要那个双重范围!

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

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

2 个答案:

答案 0 :(得分:0)

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

此代码返回“interval”(我只使用Tuple<,>,您可以使用自己的Interval类型)基于截断七个最低有效位:

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)

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

答案 1 :(得分:0)

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

您可以向下舍入下限结果,而是使用舍入到最接近结果的nextDown。如果您要对上限进行舍入,请使用舍入到最接近结果的nextUp

此方法可确保间隔继续包含确切的实数结果。它引入了额外的舍入误差 - 在某些情况下,下限将是一个小于它应该的ULP,和/或上限将是一个ULP更大。但是,它是间隔的最小扩展,远小于你在十进制中工作或通过抑制低有效位来扩展。