%"的含义在c#中用于数字类型double的操作

本文关键字:数字 用于 类型 操作 double quot | 更新日期: 2023-09-27 18:14:10

最近我发现c#的运算符%适用于double。尝试了一些东西,最后想出了这个测试:

class Program
{
    static void test(double a, double b)
    {
        if (a % b != a - b * Math.Truncate(a / b))
        {
            Console.WriteLine(a + ", " + b);
        }
    }
    static void Main(string[] args)
    {
        test(2.5, 7);
        test(-6.7, -3);
        test(8.7, 4);
        //...
    }
}

这个测试中的所有内容都有效。a % b总是等于a - b*Math.Round(a/b)吗?如果没有,请给我解释一下这个操作符是如何工作的。

编辑:回答James L, 我理解这是一个模算子和一切。我只好奇它是如何工作的双精度,我理解的整数。

%"的含义在c#中用于数字类型double的操作

模数运算符对浮点数的操作方式与对整数的操作方式相同。考虑一个简单的例子:

4.5 % 2.1
现在,4.5/2.1近似等于2.142857

所以除法的整数部分是2。4.5减去2*2.1,剩下0.3。

当然,这个过程受到浮点可表示性问题的影响,所以要小心——你可能会看到意想不到的结果。例如,请参阅这里关于堆栈溢出的问题:浮点算术-双类型的模运算符

a % b是否总是等于a - b*Math.Round(a/b)?

不,它不是。下面是一个简单的反例:

static double f(double a, double b)
{
    return a - b * Math.Round(a / b);
}
static void Main(string[] args)
{
    Console.WriteLine(1.9 % 1.0);
    Console.WriteLine(f(1.9, 1.0));
    Console.ReadLine();
}

关于如何指定模数运算符的精确细节,你需要参考c#规范——earlNameless的答案给了你一个链接。

这是我的理解,a % b基本上是等效的,模浮点精度,a - b*Math.Truncate(a/b)

选自c#语言规范第200页:

浮点剩余:

float operator %(float x, float y); 
double operator %(double x, double y); 

下表列出了非零有限值、零、无穷大和NaN的所有可能组合的结果。在表中,x和y是正有限值。z是x % y的结果,计算为x - n * y,四舍五入到最接近的可表示值,其中n是小于或等于x/y的最大整数。这种计算余数的方法类似于用于整数操作数的方法,但不同于IEC 60559定义(其中n是最接近x/y的整数)。

从MSDN页面:

模数运算符(%)计算除其后的余数第一个操作数乘以第二个操作数。所有数值类型都有预定义的模数运营商。

注意与double类型相关的舍入错误。

搜索短语"模浮点c#"会在Stack Overflow中找到相当多的条目,其中大多数都很好地解释了浮点精度如何使事情复杂化。我不认为有任何简单实用的方法可以解决这个问题。我为自己的目的想出的是以下模函数:

public static double modulo( double a, double b, double num_sig_digits = 14 )
{
   double  int_closest_to_ratio
         , abs_val_of_residue
         ;
   if ( double.IsNaN( a )
      || double.IsNaN( b )
      || 0 == b
      )
   {
      throw new Exception( "function modulo called with a or b == NaN or b == 0" );
   }
   if ( b == Math.Floor( b ) )
   {
      return (a % b);
   }
   else
   {
      int_closest_to_ratio = Math.Round( a / b );
      abs_val_of_residue = Math.Abs( a - int_closest_to_ratio * b );
      if ( abs_val_of_residue < Math.Pow( 10.0, -num_sig_digits ) )
      {
         return 0.0;
      }
      else
      {
         return abs_val_of_residue * Math.Sign( a );
      }
   }
}

以下是一些示例结果:

模(0.5,0.1,17)= 0

模(0.5,-0.1,16)= 0

模(-0.5,0.1,15)= 0

模(-0.5,-0.1,14)= 0

模(0.52,0.1,16)= 0.02

模(0.53,-0.1,15)= 0.03

模(-0.54,0.1,14)= -0.04

模(-0.55,-0.1,13)= -0.05

模(2.5,1.01,17)= 0.48

模(2.5,-1.01,16)= 0.48

模(-2.5,1.01,15)= -0.48

模(-2.5,-1.01,14)= -0.48

modulo(0.59999999999999977, 0.1, 16) = 2.35367281220533E-14

modulo(0.59999999999999977, 0.1, 15) = 2.35367281220533E-14

modulo(0.59999999999999977, 0.1, 14) = 2.35367281220533E-14

模(0.59999999999999977,0.1,13)= 0

modulo(0.59999999999999977, 0.1, 12) = 0