虽然我的问题听起来微不足道,但事实并非如此。希望你能帮帮我。
我想在.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
小数点四舍五入。看起来很棒,但不能强迫它向上/向下舍入。 Math.Round(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
附近的值,因为这会给我一个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中找不到任何方法。
那么,你有什么想法吗? 提前致谢!
答案 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更大。但是,它是间隔的最小扩展,远小于你在十进制中工作或通过抑制低有效位来扩展。