글쓴이 보관물: 소장 마

C #, Java 등에서 수학 지향 코드의 가독성을 향상시키기 위해 무엇을 할 수 있습니까? [닫은]

C 프로그래머와 C # 프로그래머 모두 C #에 대해 싫어하는 것 중 하나는 자세한 수학 함수의 정도입니다. 예를 들어 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과 같은 수학 지향 언어가 아니므로 의미가 있습니다. 그러나 때때로 수학 코드를 작성해야 할 때도 있고, 더 읽기 쉽게 만들면 좋을 것입니다.



답변

전역 정적 함수를 호출하는 로컬 함수를 정의 할 수 있습니다. 컴파일러가 래퍼를 인라인하고 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);
    }
}

일반적인 수학 연산을 단일 연산으로 묶는 함수를 만들 수도 있습니다. 이렇게하면 코드 에서 함수가 유사 sin하고 cos코드에 나타나는 인스턴스 수를 최소화하여 전역 정적 함수를 호출하는 번거 로움이 줄어 듭니다. 예를 들면 다음과 같습니다.

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)
}

점과 회전 수준에서 작업하고 있으며 기본 삼각 함수가 묻혀 있습니다.


답변

Java에는 특정 사항을 덜 장황하게하는 데 사용할 수있는 많은 도구가 있습니다.이를 알고 있으면됩니다. 이 경우에 유용한 것은 static가져 오기 ( tutorial page , 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 클래스의 정적 가져 오기를 수행하는 데 약간 의 부담이 있지만 많은 수학을 수행하는 경우 호출 될 수 있습니다.

정적 가져 오기를 사용하면 정적 필드 또는 메소드를이 클래스의 네임 스페이스로 가져 와서 패키지 이름없이 호출 할 수 있습니다. Junit 테스트 사례에서 import static org.junit.Assert.*;모든 어설 션을 사용할 수 있는 경우가 종종 있습니다.


답변

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 
        }
    }
}

참조 : 정적 문 (AC # 6.0 언어 미리보기)를 사용하여

또 다른 C # 6.0 “구문 설탕”기능은 정적을 사용하는 것입니다. 이 기능을 사용하면 정적 메소드를 호출 할 때 유형에 대한 명시 적 참조를 제거 할 수 있습니다 . 또한 static을 사용하면 네임 스페이스 내의 모든 확장 메서드가 아닌 특정 클래스에 대한 확장 메서드 만 도입 할 수 있습니다.

편집 : Visual Studio 2015, 2015 년 1 월 CTP가 출시 된 이후 정적 가져 오기에는 명시 적 키워드가 필요합니다 static. 처럼:

using static System.Console;

답변

여기에있는 다른 좋은 답변 외에도 상당한 수학적 복잡성 (평균 사용 사례가 아니라 일부 금융 또는 학술 프로젝트)에 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을 통해 비즈니스 사람들이나 학술 연구원이 공식 문서를 편안하게 유지할 수있는 경우 큰 이점을 얻을 수 있습니다 그들의 작업이 프로젝트의 구현 언어로 정확하게 번역되었다는 것을 확신하십시오.


답변

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() ...

답변

C # : Randall Cook의 답변 에 대한 변형은 확장 방법보다 코드의 수학적 “모양”을 유지하기 때문에 래퍼를 사용하는 대신 랩을 사용하는 대신 함수 참조를 사용하는 것입니다. 개인적으로 코드가 더 깨끗해 보이지만 기본적으로 같은 일을하고 있습니다.

랜달의 랩핑 된 함수, 함수 참조 및 직접 호출을 포함한 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

답변

스칼라를 사용하십시오! 기호 연산자를 정의 할 수 있으며 메소드에 대한 구문 분석이 필요하지 않습니다. 이것은 수학 만드는 방법을 쉽게 해석 할 수.

예를 들어, 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));
}

이것은 꽤 빨리 축적됩니다.