在C#中,我该如何实现像Google计算器一样的模运算?

7
我有一个代表形状的类。该Shape类有一个名为Angle的属性。我希望此属性的setter自动将值包装到[0,359]范围内。
不幸的是,简单的_Angle = value % 360;仅适用于正数。在C#中,-40 % 360 == -40。谷歌计算器按我想要的方式进行计算。该值应为320。
在C#中,最优雅的解决方案是什么?
以下是我迄今为止得到的最佳方法:
     public double Angle {
        get { return _Angle; } 
        set {
            if ( value >= 0 ) {
                _Angle = value % 360;
            }
            else {
                _Angle = value - (360 * ((int)(value / 360) - 1)); 
            }
        }
    }

编辑:

谢谢大家,我现在有:

     public double Angle {
        get { return _Angle; } 
        set {
            _Angle = (value % 360) + ((value < 0) ? 360 : 0);
        }
    }

这要好得多 :)


1
我强烈反对使用模运算,因为硬件模/除法很慢。如果您能够将数据缩放为2的幂,则可以使用更好的解决方案,利用位掩码。 - Trevor Boyd Smith
@TrevorBoydSmith:C#编译器在整数除法和取模运算时不能进行位运算优化吗?在浮点数情况下,您能否进行位运算优化? - Sebastian Mach
7个回答

10
尽管这是针对Java的,但Java在取余运算时也具有相同的行为(即-40%360 == -40)。
无论给定的角度是正数还是负数,下面的代码应该返回一个[0, 360)之间的答案。
public class Mod
{
    public static int mod(int a, int b)
    {
        if (a < 0)
            return b + (a % b);
        else
            return a % b;
    }

    public static void main(String[] args)
    {
        System.out.println(mod(40, 360));   // 40
        System.out.println(mod(-40, 360));  // 320
        System.out.println(mod(-400, 360)); // 320
    }
}

请注意,当给定的角度超过-360度时,该函数才能正常使用。


哇,这也太复杂了吧,你完全不需要那个额外的if语句。 - Nick Berardi

4

虽然您的解决方案适用于您遇到的问题,但实际上该算法与Google使用的算法并不完全相同。如果使用负除数,则会有所不同。

public double GoogleModulo(double value, double divisor)
{
    long q = (long)Math.Floor(value / divisor);
    return value - q * divisor;
}

Console.WriteLine(GoogleModulo(  40,  360)); //   40
Console.WriteLine(GoogleModulo( -40,  360)); //  320
Console.WriteLine(GoogleModulo(-400,  360)); //  320
Console.WriteLine(GoogleModulo(  40, -360)); // -320

查看谷歌对上一个计算的响应此处

该算法在维基百科上有详细解释,并归功于Donald Knuth。


3
这应该可以为您提供所需的结果。
public double Angle {
    get { return _Angle; }
    set { _Angle = value % 360 + (value % 360 < 0 : 360 : 0); }
}

我假设360度是指角度,您想找到角度在{0, 360}中的位置。

1
(-1) 你因为 coobird 写了一个不必要的 if 而责骂他,但是你自己的代码(打错了)使用了 ?:. 你认为编译器不会为此生成一个条件语句吗? - RAL
1
好的,回到你的问题上,问题不在于他如何使用IF语句,而在于他没有满足帖子的要求。他已经有了一个IF语句,作者想要最小化他的代码,添加另一种带有IF语句的方法并不能做到这一点。 - Nick Berardi
1
你这里实际上还有一个额外的“%” - 当“value”小于零时,“value%360”小于零。 - Blorgbeard

2

模运算非常慢。如果可能,请使用位掩码替换。

coobird的代码非常好... 但是由于它执行了模运算,所以非常慢。如果可以将数据缩放到某个2的幂范围内,则可以通过使用位掩码将速度提高大约一个数量级(至少快2或3倍)。

C代码:

#define BIT_MASK (0xFFFF)
if (a < 0) {
    return b + (a & BIT_MASK);
} else {
    return a & BIT_MASK;
}

可以随意将#define定义为运行时内容。并且您可以随意调整位掩码,使其成为您需要的任何2的幂次方,例如0xFFFFFFFF或您决定实现的2的幂次方。


除非角度设置占用了大量的CPU时间,否则我认为这并不值得努力。我的当前计算机可以使用2个线程进行小角度下的450百万模数运算。使用位运算速度快2.5-3倍。在100万次操作中,仅节省1.5毫秒。 - ggf31416
如果增加2到3倍的性能不算“好”,那么你面临的问题似乎是在处理一些小问题(例如数值包装)时过早优化,而应该把重点放在更大的问题上。我所从事的许多工作都有一个硬实时截止时间,并且必须在微秒内执行。 - Trevor Boyd Smith

1

// 转一圈

set { _Angle = (value + 360) % 360 }


我想到了,但是如果传递一个小于-360的值怎么办? - Blorgbeard
是的,我在这里假设每次更新时角度已经被规范化了。 - ja.

-1
(360 * Math.floor(Math.abs(value) / 360) + value) % 360

-1
如果您的值不会超出范围,您可以做一个小循环。
while (value < 0) {
  value = value + 360;
}
while (value > 360) {
  value = value - 360;
}

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