web-dev-qa-db-ja.com

C#やJavaなどで数学指向のコードを読みやすくするために何ができますか?

CプログラマーとC#プログラマーの両方にとって、C#について私が気に入らない点の1つは、詳細な数学関数がいかにあるかです。たとえば、Sin、cosine、またはpower関数を使用する必要があるたびに、Math静的クラスを付加する必要があります。方程式自体が非常に単純な場合、これは非常に長いコードになります。データ型を型キャストする必要がある場合、問題はさらに悪化します。結果として、私の意見では、読みやすさが低下します。例えば:

double x =  -Math.Cos(X) * Math.Sin(Z) + Math.Sin(X) * Math.Sin(Y) * Math.Cos(Z);

単純にではなく

double x = -cos(X) * sin(Z) + sin(X) * sin(Y) * cos(Z);

これは、Javaなどの他の言語でも同じです。

この質問に実際に解決策があるかどうかはわかりませんが、C#やJava=プログラマーが数学コードを読みやすくするために使用するトリック)があるかどうか知りたいのですが、C#/Java/etc。は、MATLABなどの数学指向の言語ではないので、理にかなっていますが、場合によっては、数学コードを記述する必要があり、読みやすくすることができればすばらしいでしょう。

16
9a3eedi

グローバル静的関数を呼び出すローカル関数を定義できます。うまくいけば、コンパイラーがラッパーをインライン化し、JITコンパイラーが実際の操作用のタイトなアセンブリコードを生成することを望みます。例えば:

class MathHeavy
{
    private double sin(double x) { return Math.sin(x); }
    private double cos(double x) { return Math.cos(x); }

    public double foo(double x, double y)
    {
        return sin(x) * cos(y) - cos(x) * sin(y);
    }
}

また、一般的な数学演算を1つの演算にまとめる関数を作成することもできます。これにより、sincosなどの関数がコード内に出現するインスタンスの数が最小限に抑えられ、それによってグローバル静的関数を呼び出すことの不格好さが目立たなくなります。例えば:

public Point2D rotate2D(double angle, Point2D p)
{
    double x = p.x * Math.cos(angle) - p.y * Math.sin(angle);
    double y = p.x * Math.sin(angle) + p.y * Math.cos(angle);

    return new Point2D(x, y)
}

ポイントと回転のレベルで作業していて、基になるトリガー関数は埋め込まれています。

14
Randall Cook

Javaの中には、特定のものを冗長にしないために利用できる多くのツールがあり、あなたはそれらに注意する必要があります。この場合に役立つのは、staticインポート( チュートリアルページwikipedia )のインポートです。

この場合、

import static Java.lang.Math.*;

class Demo {
    public static void main (String[] args) {
        double X = 42.0;
        double Y = 4.0;
        double Z = PI;

        double x =  -cos(X) * sin(Z) + sin(X) * sin(Y) * cos(Z);
        System.out.println(x);
    }
}

かなりうまく動作します( ideone )。 Mathクラスのallの静的インポートを実行するのは少し重いですが、多くの計算を実行している場合、それは呼び出される可能性がありますために。

静的インポートを使用すると、静的フィールドまたはメソッドをこのクラスの名前空間にインポートし、パッケージ名を必要とせずに呼び出すことができます。これは、import static org.junit.Assert.*;がすべての asserts を取得できることがわかっているJunitテストケースでよく見られます。

31
user40980

C#6.0では、静的インポートの機能を使用できます。

あなたのコードは次のようになります:

using static System.Math;
using static System.Console;
namespace SomeTestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            double X = 123;
            double Y = 5;
            double Z = 10;
            double x = -Cos(X) * Sin(Z) + Sin(X) * Sin(Y) * Cos(Z);
            WriteLine(x); //Without System, since it is imported 
        }
    }
}

参照: Static Using Statements(A C#6.0 Language Preview)

C#6.0のもう1つの「構文糖」機能は、staticの使用の導入です。この機能を使用すると、静的メソッドを呼び出すときに、タイプへの明示的な参照を削除することができます。さらに、staticを使用すると、名前空間内のすべての拡張メソッドではなく、特定のクラスの拡張メソッドのみを導入できます。

編集:Visual Studio 2015以降、CTPは2015年1月にリリースされたため、静的インポートには明示的なキーワードstaticが必要です。お気に入り:

using static System.Console;
5
Habib

ここでの他の良い答えに加えて、私は [〜#〜] dsl [〜#〜] がかなりの数学的複雑さ(平均的なユースケースではなく、おそらくいくつかの財政的または学術的プロジェクト)。

Xtext などのDSL生成ツールを使用すると、独自の簡略化された数学的文法を定義できます。これにより、Java(または他の言語を含む) )数式の表現。

DSL式:

domain GameMath {
    formula CalcLinearDistance(double): sqrt((x2 - x1)^2 + (y2 - y1)^2)
}

生成された出力:

public class GameMath {
    public static double CalcLinearDistance(int x1, int x2, int y1, int y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    }
}

このような単純な例では、文法とEclipseプラグインを作成するメリットは価値がありませんが、より複雑なプロジェクトでは、特にDSLがビジネススタッフや学術研究者が正式なドキュメントを快適に維持できる場合、大きなメリットが得られます。言語、そして彼らの仕事がプロジェクトの実装言語に正確に翻訳されたことを確認してください。

4
Dan1701

C#では、拡張メソッドを使用できます。

以下は、 "postfix"表記に慣れると、とても読みやすくなります。

_public static class DoubleMathExtensions
{
    public static double Cos(this double n)
    {
        return Math.Cos(n);
    }

    public static double Sin(this double n)
    {
        return Math.Sin(n);
    }

    ...
}

var x =  -X.Cos() * Z.Sin() + X.Sin() * Y.Sin() * Z.Cos();
_

残念ながら、ここで負の数を扱う場合、演算子の優先順位により、少し醜くなります。 Math.Cos(-X)の代わりに-Math.Cos(X)を計算する場合は、数値を括弧で囲む必要があります。

_var x = (-X).Cos() ...
_
4
rjnilsson

C#: Randall Cookの回答 のバリエーションは、拡張メソッドよりもコードの数学的な「外観」を維持するため、ラッパーを使用しますが、ラップではなく呼び出しに関数参照を使用しますそれら。個人的にはコードがきれいに見えると思いますが、基本的に同じことをしています。

Randallのラップされた関数、関数参照、および直接呼び出しを含む小さなLINQPadテストプログラムを作成しました。

関数参照呼び出しは、基本的に直接呼び出しと同じ時間を要します。ラップされた関数は一貫して遅くなりますが、それほどではありません。

これがコードです:

void Main()
{
    MyMathyClass mmc = new MyMathyClass();

    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();

    for(int i = 0; i < 50000000; i++)
        mmc.DoStuff(1, 2, 3);

    "Function reference:".Dump();
    sw.Elapsed.Dump();      
    sw.Restart();

    for(int i = 0; i < 50000000; i++)
        mmc.DoStuffWrapped(1, 2, 3);

    "Wrapped function:".Dump();
    sw.Elapsed.Dump();      
    sw.Restart();

    "Direct call:".Dump();
    for(int i = 0; i < 50000000; i++)
        mmc.DoStuffControl(1, 2, 3);

    sw.Elapsed.Dump();
}

public class MyMathyClass
{
    // References
    public Func<double, double> sin;
    public Func<double, double> cos;
    public Func<double, double> tan;
    // ...

    public MyMathyClass()
    {
        sin = System.Math.Sin;
        cos = System.Math.Cos;
        tan = System.Math.Tan;
        // ...
    }

    // Wrapped functions
    public double wsin(double x) { return Math.Sin(x); }
    public double wcos(double x) { return Math.Cos(x); }
    public double wtan(double x) { return Math.Tan(x); }

    // Calculation functions
    public double DoStuff(double x, double y, double z)
    {
        return sin(x) + cos(y) + tan(z);
    }

    public double DoStuffWrapped(double x, double y, double z)
    {
        return wsin(x) + wcos(y) + wtan(z);
    }

    public double DoStuffControl(double x, double y, double z)
    {
        return Math.Sin(x) + Math.Cos(y) + Math.Tan(z);
    }
}

結果:

Function reference:
00:00:06.5952113

Wrapped function:
00:00:07.2570828

Direct call:
00:00:06.6396096
2
Whelkaholism

Scalaを使用してください!記号演算子を定義でき、メソッドに括弧は必要ありません。これにより、数学wayの解釈が容易になります。

たとえば、ScalaおよびJavaでの同じ計算は次のようになります。

// Scala
def angle(u: Vec, v: Vec) = (u*v) / sqrt((u*u)*(v*v))

// Java
public double angle(u: Vec, v: Vec) {
  return u.dot(v) / sqrt(u.dot(u)*v.dot(v));
}

これはすぐに追加されます。

1
Rex Kerr