在C#中对十进制数据类型执行数学运算?

22

我在想上述内容是否有可能实现。例如:

Math.Sqrt(myVariableHere);

当查看重载时,它需要一个双精度浮点数参数,因此我不确定是否有其他方法可以使用十进制数据类型来复制这个过程。

5个回答

64

我不明白为什么那个问题的所有答案都是一样的。

有几种方法可以从一个数字中计算平方根。其中一种是由艾萨克·牛顿提出的。我只会写其中最简单的实现方式之一。我使用它来提高双精度浮点数的平方根精度。

// x - a number, from which we need to calculate the square root
// epsilon - an accuracy of calculation of the root from our number.
// The result of the calculations will differ from an actual value
// of the root on less than epslion.
public static decimal Sqrt(decimal x, decimal epsilon = 0.0M)
{
    if (x < 0) throw new OverflowException("Cannot calculate square root from a negative number");

    decimal current = (decimal)Math.Sqrt((double)x), previous;
    do
    {
        previous = current;
        if (previous == 0.0M) return 0;
        current = (previous + x / previous) / 2;
    }
    while (Math.Abs(previous - current) > epsilon);
    return current;
}

关于速度:在最坏的情况下(epsilon = 0且number为decimal.MaxValue),循环不到三次。

如果您想了解更多,请阅读这本书(《Hacker's Delight》by Henry S. Warren, Jr.)


7

我刚刚看到这个问题,我建议使用一种不同于SLenik提出的算法。这是基于巴比伦方法

public static decimal Sqrt(decimal x, decimal? guess = null)
{
    var ourGuess = guess.GetValueOrDefault(x / 2m);
    var result = x / ourGuess;
    var average = (ourGuess + result) / 2m;

    if (average == ourGuess) // This checks for the maximum precision possible with a decimal.
        return average;
    else
        return Sqrt(x, average);
}

这不需要使用现有的Sqrt函数,从而避免了转换为double并返回时伴随的精度损失。


Bobson,你提出了另一种可能的解决方案,但是SLenik的解决方案没有任何缺陷,因为他只是使用双倍Sqrt(double)作为起点——就像一个种子。然后,在循环中,他使用完全精度的小数“x”来调整Sqrt的结果值。 - farfareast
@farfareast - 这是一个有道理的观点,在大多数情况下,精度问题并不重要。但如果你一开始就有一个非常精确的小数,你会失去其中的一部分,这可能会导致结果略微偏差。我也认为通过使用平方根来计算平方根在哲学上是不可接受的,尽管如果我真的需要它并且有性能原因需要这样做,那么这当然不会阻止我编写代码。 - Bobson
在我的初步测试(和发布后)中,我的算法似乎快了100倍左右,但经过进一步的测试,我发现测试的顺序会影响结果,所以我将其删除了。它仍然是一个有效的算法,我认为它稍微快一点,但只是一个小差别。内置的double版本的Sqrt仍然比任何其他方法都要快得多。 - Bobson
不难理解为什么双倍平方根较快。看看这篇维基百科文章中的引用:“浮点运算指令x86汇编语言包括基于堆栈的浮点处理器。它们包括加法、减法、取反、乘法、除法、余数、平方根、整数截断、分数截断和二次幂缩放。” 特别是在英特尔机器代码指令集中有FSQRT指令。 :-) 另请参见:此处 - farfareast
1
我认为这是一个非常棒的解决方案。谢谢。 - Sachin Kainth
在我的机器上使用BenchmarkDotNet进行基准测试显示,这个解决方案比SLenik的解决方案慢,后者的平均时间为250纳秒,而这个解决方案的平均时间为1400纳秒。仅供参考,Math.Sqrt的平均时间为1纳秒。 - undefined

5
在大多数涉及到十进制数(货币等)的情况下,取根并不是有用的;而且根本不会像你期望的十进制数那样具有预期精度。当然,你可以通过强制转换来实现(假设我们没有处理十进制数范围的极端端点):
decimal root = (decimal)Math.Sqrt((double)myVariableHere);

这将迫使您至少承认固有的四舍五入问题。


7
我正在制作一个可以计算黄金比例的应用程序,最终将转换为小数范围。在我的情况下无法工作。 - ave

-3
简单来说:将您的 decimal 转换为 double 并调用函数,获取结果并将其转换回 decimal。这可能比您自己编写任何 sqrt 函数都要快,并节省大量精力。

-4
Math.Sqrt((double)myVariableHere);

将返回一个双精度浮点数,它是您的decimal myVariableHere的平方根。


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