C# の double の %

C# 5.0 の double の % がなんか仕様とは異なる値を返す気がするという話。

Download C# Language Specification 5.0 from Official Microsoft Download Center C# 5.0 の言語仕様によれば、

7.8.3 Remainder operator

  • Floating-point remainder:
double operator %(double x, double y);

z is the result of x % y and is computed as x - n * y,
where n is the largest possible integer that is less than or equal to x / y.

とのことなので、n = Floor(x / y) のハズ...。でも実際は Truncate っぽい動作をする。

static void ReminderOperatorDoubleFloor()
{
    var x = -5.2;
    var y = 2.0;
    var n = System.Math.Floor(x / y);
    var z = x - n * y;
    Console.Out.WriteLine("FLOOR: x = {0}, y = {1}, n = {2}, z = {3}, x % y = {4}", x, y, n, z, x % y);
    // => FLOOR: x = -5.2, y = 2, n = -3, z = 0.8, x % y = -1.2
}

static void ReminderOperatorDoubleTruncate()
{
    var x = -5.2;
    var y = 2.0;
    var n = System.Math.Truncate(x / y);
    var z = x - n * y;
    Console.Out.WriteLine("TRUNCATE: x = {0}, y = {1}, n = {2}, z = {3}, x % y = {4}", x, y, n, z, x % y);
    // => TRUNCATE: x = -5.2, y = 2, n = -2, z = -1.2, x % y = -1.2
}

static void ReminderOperatorInt()
{
    var x = -52;
    var y = 20;
    var n = x / y;
    var z = x - (x / y) * y;
    Console.Out.WriteLine("INT: x = {0}, y = {1}, n = {2}, z = {3}, x % y = {4}", x, y, n, z, x % y);
    // => INT: x = -52, y = 20, n = -2, z = -12, x % y = -12
}

で、実際どんな式が使われてるの、というと Math.IEEERemainder(Double, Double) Method (System) | Microsoft Docs の Remarks に書かれているが、Abs を使った上で Floor して符号は後から追加している。でも仕様には Abs 使うなんて書いてないじゃん!*1

# 蛇足ですが C++03 では double の % 演算は定義されておらずコンパイルできない。

*1:7.8.2 Division operator の整数の / 演算子のところには "absolute value" うんぬんとの記述がみられるので、やはり % のほうの記述は書き忘れであるように思う