C#中的大整数除法

13
我正在编写一个需要在C#中准确执行大整数除法的类。
例子:
BigInteger x = BigInteger.Parse("1000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
BigInteger y = BigInteger.Parse("2000000000000000000000000000000000000000000000000000000000000000000000000000000000000");

x /= y;

Console.WriteLine(x.ToString());

//Output = 0
问题在于整数类型的数据自然不包含小数值。如何克服这个问题,以便获得0.5的实际结果(给定示例)。
注:解决方案必须能够准确地将任何BigInteger进行除法运算,而不仅仅是适用于此示例!

1
你需要结果有多少位数字?通常情况下,当你除以两个数,比如 10/7,数学结果会有无限多的小数。这在计算机中很难表示。 - Jeppe Stig Nielsen
注意:这可能接近我正在寻找的答案:https://dev59.com/eHE85IYBdhLWcg3wQxPG - Matthew Layton
1
对于太多小数位,使用Math.Round(result,2)例如2位数字。 - abc
@AVD 在这个例子中,数字不能直接适用于 System.Decimal,因此您需要提供更多细节。 - Jeppe Stig Nielsen
2
我知道这不是一个完全正统的想法...但我能否在C#中移植Java的BigDecimal类并使用它? - Matthew Layton
显示剩余7条评论
7个回答

17

在上面的例子中,这些数字仍然很小,可以转换为double,因此在这种情况下,你可以说

double result = (double)x / (double)y;

如果xy过大,超出了double类型的表示范围,但仍然可以进行比较,那么这个巧妙的技巧也许会有所帮助:

double result = Math.Exp(BigInteger.Log(x) - BigInteger.Log(y));

但是一般来说,当BigInteger非常大,它们的商也非常大时,如果不导入第三方库,这很难实现。


这似乎是我能找到的最接近的东西。在我的实现中它正在工作! - Matthew Layton
当然,会失去很多精度。但是最后一种方法在某些情况下仍然有效。例如,如果 x 有1005位数字,y 有1000位数字,则两者都无法转换为 double,但 result 仍然可以(小数点前约有5位数字)。但是,如果 x 有2007位数字,y 有1000位数字,则 result 将为无穷大。不会抛出任何异常。注意: 如果 xy 可能为非正数,则必须取绝对值,并在最后处理符号。 - Jeppe Stig Nielsen

8

你需要对除法的精度有何要求?一种方法是:

  • 将分子乘以1000
  • 将两个数字相除
  • 将结果转换为double并除以1000

同样的代码:

BigInteger x = BigInteger.Parse("1000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
BigInteger y = BigInteger.Parse("2000000000000000000000000000000000000000000000000000000000000000000000000000000000000");

x *= 1000;
x /= y;
double result = (double)x;
result /= 1000;
Console.WriteLine(result);

另一种写法是:double result = (double)(1000 * x / y) / 1000.0;(不会重新分配 x)。result 可能变成 PositiveInfinityNegativeInfinity - Jeppe Stig Nielsen

1

如果您需要保持完整的精度,请使用有理数的实现(Java 的等效类是 Apache Commons Math 库中的 Fraction 类)。有各种不同的实现方法,但对于 .NET 4.0 来说最轻量级的解决方案是以下内容:

        System.Numerics.BigInteger x = System.Numerics.BigInteger.Parse("10000000000000000000000000000000000000000000000000000");

        System.Numerics.BigInteger y = System.Numerics.BigInteger.Parse("20000000000000000000000000000000000000000000000000000");

        // From BigRationalLibrary
        Numerics.BigRational r = new Numerics.BigRational(x,y);

        Console.Out.WriteLine(r.ToString());
        // outputs "1/2", but can be converted to floating point if needed.

要使其工作,您需要从.Net 4.0 System.Numerics.dll获取System.Numberics.BigInteger以及来自CodePlex的BigRational实现。

Microsoft Solver Foundation 3.0中还实现了有理数结构。在撰写本文时,www.solverfoundation.com网站已经崩溃,因此提供一个指向存档的链接。


好的,谢谢你指出这个问题,我漏掉了那个细节。 - jpe
它提出了一个很好的观点!为什么C#没有这些类呢?唉! - Matthew Layton
我编辑了我的答案以反映语言上的“轻微”差异。似乎 C# 也在朝着正确的方向发展! - jpe

0
//b = 10x bigger as a => fraction should be 0.1
BigInteger a = BigInteger.Pow(10, 5000);
BigInteger b = BigInteger.Pow(10, 5001);

//before the division, multiple by a 1000 for a precision of 3, afterwards 
//divide the result by this.
var fraction = (double) BigInteger.Divide(a * 1000, b) / 1000;

0

听起来像是一个定点数的工作(而不是浮点数)。

只需将分子按所需小数位数进行预移位,就像这样:

BigInteger quotient = (x << 10) / y;

这将给您小数点后10位(约3个十进制数字)。


0

正如您所知,整数除法不会产生小数值,因此您的结果被截断为0。根据这个问题,可以在这里找到大数双精度实现,但其最后一个版本是在2009年发布的。如果您继续寻找,可能会找到更新的版本,或者这个版本已经完成了。


-1

将其解析为双精度数:

double a = Convert.ToDouble(x);
double b = Convert.ToDouble(y);

Console.WriteLine(a / b);

我使用了相同的想法。但是,我不确定Convert.ToDouble是否可行,但这只是一个细节。 - Jeppe Stig Nielsen
BigInteger没有实现IConvertible接口 :-( 没有这样的运气! - Matthew Layton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接