C# 通过正确的四舍五入实现精确的双精度

C# 通过正确的四舍五入实现精确的双精度,c#,floating-point,rounding,intervals,C#,Floating Point,Rounding,Intervals,虽然我的问题听起来微不足道,但事实并非如此。希望你能帮助我 我想在.NET(C#)项目中实现区间算法。这意味着每个数字都由一个下界和一个上界定义。这对以下问题很有帮助 1 / 3 = 0.333333333333333 (15 significant digits) 因为那时你会 1 / 3 = [ 0.33333333333333 , 0.333333333333334 ] (14 significant digits each) ,所以我现在可以肯定,正确的答案就在这两个数字之间。如果没

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

我想在.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)
方法,它将
double
四舍五入到
int
小数位。看起来不错,但不能强制向上/向下取整<代码>数学。舍入(1.0/3.0,14)将向下舍入,但也需要向上舍入到0.33…34不能像这样实现

但是有
Math.Ceil
Math.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
的值,因为这会给我一个
溢出异常。所以这也不是解决办法

而且,最重要的是:在尝试四舍五入
0.999999999999999999999999999(超过15位)
时,所有这些都会更加失败,因为在我开始尝试四舍五入之前,内部表示已经四舍五入为1

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

Decimal
也不起作用,因为我不想要那么高的精度,14位数就足够了;但我还是想要双倍射程

在C++中,在存在多个区间算术实现的情况下,通过将处理器动态地告知其圆周模式,例如“总是向下”或“总是向上”,可以解决这个问题。我在.NET中找不到任何方法来实现这一点

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

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

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

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));
}
静态元组GetMinMaxIntervalBasedOnBinaryNumbers,在七个字节(双倍数字)上循环
{
if(double.isnfinity(number)| double.IsNaN(number))
return Tuple.Create(number,number);//可能会以不同的方式处理此情况
var i=位转换器。DoubleToInt64位(数字);
const int numberOfBitsToClear=7;//您的七,可以更改此值,必须低于52
const long precision=1L假设
nextDown(x)
是一个返回小于
x
的最大双精度的函数,
nextUp(x)
是一个返回大于
x
的最小双精度的函数。有关实现思想,请参阅

如果将下限结果向下四舍五入,则使用四舍五入至最近结果的回合的
nextDown
。如果将上限结果向上四舍五入,则使用四舍五入至最近结果的回合的
nextUp


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

我可能误解了你的问题,但你不能简单地将
1/(Math.Pow(10,nDecimalPlaces))
添加到
Math.Round
结果中吗?如果结果是0.999999999999999999,这将失败,因为C#将其舍入1。然后我将得到[0.9999999999999999999999999999999,1.00000001]这很好,但不是很好,因为我在上限上失去了精确性(上限为1就足够了)。我不确定这是怎么做的。但当你想向上或向下取整时,为什么要舍入到14位小数?对于像
double
这样的二进制类型来说没有多大意义。相反,你可以舍入到46位有效位。这似乎更“统一”。编辑:不要忘记像
0.33333333 4
(14位小数)这样的数字不能精确地表示为二进制类型,如
double
。如果改为使用二进制“截止”,如46位或其他,则区间的端点将为“圆形”在二进制意义上。14源于double对多达15个有效数字的精确性。因为第15个数字可能是四舍五入的,所以我想确保这个数字不计数,所以只取14。不确定这是否有效,但这个问题显示了如何调用
\u control87
(可用于更改四舍五入模式)来自C#:谢谢你的努力!我也在玩BitConverter一段时间了。你的方法对于大的输入失败。9999999999999为Exmaple返回9999999999.016。错误0.016已经相当大了…@selmaohneh这就是我想要的!不,
0.016
在那种情况下很小!你在你的问题中说你不想多重输入吗使用
1E14
,因为它可能会溢出到无穷大。这意味着您需要处理巨大的数字。例如,您希望包含数字
3.14E298
。我的