【问题标题】:In C#, how do I implement modulus like google calc does?在 C# 中,如何像 google calc 那样实现模数?
【发布时间】:2010-10-03 11:38:05
【问题描述】:

我有一个代表形状的类。 Shape 类有一个名为 Angle 的属性。我希望此属性的设置器自动将值包装到 [0,359] 范围内。

不幸的是,简单的_Angle = value % 360; 仅适用于正数。在 C# 中,-40 % 360 == -40。谷歌计算它the way I want it。该值应为 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);
        }
    }

..哪个更好:)

【问题讨论】:

  • 我强烈反对使用 mod 操作,因为硬件 mod/division 很慢。如果您能够将数据扩展为 2 的幂,则可以使用更好的利用位掩码的解决方案。
  • @TrevorBoydSmith:C# 编译器是否不允许在整数的情况下优化除法和模到位运算?在浮点值的情况下你能优化位操作吗?

标签: c# modulo


【解决方案1】:

虽然这适用于 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 语句。
【解决方案2】:

虽然您的解决方案适用于您遇到的问题,但算法实际上与 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

查看谷歌对上次计算here的回复。

该算法在 wikipedia 上进行了解释,并归功于 Donald Knuth。

【讨论】:

    【解决方案3】:

    这应该会给你所需的结果

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

    我假设 360 度是度数,而您试图找出角度在 {0, 360} 中的位置。

    【讨论】:

    • (-1) 你斥责 coobird 一个不需要的 if,并且你的代码(输入错误)使用 ?:。你认为编译器不会为此生成条件吗?
    • 好吧 (-1) 回到你的问题上,这不是他如何使用 IF 语句,而是他没有满足发帖者的要求。他已经有一个 IF 语句,作者想最小化他的代码,添加另一个带有 IF 语句的方法并没有这样做
    • 你实际上也有一个额外的% - 如果value 小于零,value % 360 小于零。
    【解决方案4】:

    模组操作很慢。如果可能,用位掩码替换。

    coobird 的代码很不错...但是很慢,因为它正在执行 mod 操作。如果可以将您的数据缩放到 2 的某个幂范围内,那么您可以通过使用位掩码将速度提高大约一个数量级(至少快 2 或 3 倍)。

    C 代码:

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

    随意将#define 设置为运行时。并随意将位掩码调整为您需要的任何二的幂。比如 0xFFFFFFFF 或您决定实施的 2 的幂。

    【讨论】:

    • 除非角度设置占用大量 CPU 时间,否则我认为不值得。我目前的计算机可以使用 2 个线程对小角度进行 450 毫米模数运算。使用位操作的速度提高了 2.5-3 倍。在 1 百万次操作中,它仅节省 1.5 毫秒。
    • 如果 2 到 3 倍的性能提升不是“好”,那么当您应该专注于更大的问题时,您的问题听起来像是在对一些小事情(即包装数字)进行过早优化。我从事的许多工作都有严格的实时截止日期,并且必须在微秒内执行。
    【解决方案5】:

    // go 'round once

    设置 { _Angle = (值 + 360) % 360 }

    【讨论】:

    • 我想到了,但是如果传递一个小于-360的值呢?
    • 是的,我在这里假设角度在每次更新时都会标准化。
    【解决方案6】:
    (360 * Math.floor(Math.abs(value) / 360) + value) % 360
    

    【讨论】:

      【解决方案7】:

      如果您的值不会超出范围,您可以做一个小循环。

      while (value < 0) {
        value = value + 360;
      }
      while (value > 360) {
        value = value - 360;
      }
      

      【讨论】:

        猜你喜欢
        • 2010-10-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-18
        • 2015-04-10
        • 2019-12-03
        • 1970-01-01
        相关资源
        最近更新 更多