【发布时间】:2017-06-10 08:15:27
【问题描述】:
虽然我的问题听起来微不足道,但事实并非如此。希望你能帮助我。
我想在我的 .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。
我可以尝试以某种方式解析包含双精度的字符串,但这无济于事,因为 (1/3 * 3).ToString() 已经打印出 1 而不是 0.99...9。
Decimal 也不起作用,因为我不想要那么深的精度,14 位就足够了;但我仍然想要那个双倍范围!
在存在多个区间算术实现的 C++ 中,可以通过动态通知处理器将其循环模式切换为例如“始终向下”或“始终向上”来解决此问题。我在 .NET 中找不到任何方法。
那么,你有什么想法吗? 提前致谢!
【问题讨论】:
-
我可能误解了你的问题,但你不能简单地将
1/(Math.Pow(10, nDecimalPlaces))添加到Math.Round结果中吗? -
当结果为 0.999999999999999 时失败,因为 C# 将其四舍五入为 1。然后我会得到 [ 0.99999999999999 , 1.00000000000001 ] 这没关系,但不是那么好,因为我失去了上限的精度(1作为上限就足够了)。
-
我不确定interval arithmetic 是这样的。但是当你想向上或向下舍入时,为什么要四舍五入到 14 个十进制数字呢?对于像
double这样的二进制类型没有多大意义。相反,您可以四舍五入说 46 个有效位。这似乎更“统一”。 编辑:不要忘记像0.333333333333334(14 位小数)这样的数字不能用像double这样的二进制类型精确表示。相反,如果您使用二进制“截止”,例如 46 位或其他任何值,则间隔的端点将是二进制意义上的“圆形”。 -
14 来自这样一个事实,即双精度最高可达 15 位有效数字。因为第 15 位可能会四舍五入,所以我想确保这个数字不算数,所以只取 14。
-
不确定这是否有效,但这个问题显示了如何从 C# 调用
_control87(可用于更改舍入模式):stackoverflow.com/questions/18811466/…
标签: c# floating-point rounding intervals