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


16

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과 같은 수학 지향 언어가 아니므로 의미가 있습니다. 그러나 때때로 수학 코드를 작성해야 할 때도 있고, 더 읽기 쉽게 만들면 좋을 것입니다.


구체적으로 알지 못하지만 성능 저하가 있지만 문자열로 수학 함수를 정의 할 수있는 대수 라이브러리를 찾을 수 있습니다.
raptortech97


7
약간의 추가 정보가 걱정되지만 단항 연산자를 사용하여 '*'중 '+'를 숨기지 않고 숨길 수 있습니다. 우선 순위가 잘못되었다고 생각합니다.
mattnz

1
그냥 예를 들어,하지만 좋은 지적이었다
9a3eedi

6
C # 6.0에서는 다음과 같이 작성할 수 있습니다 using System.Math; … double x = -Cos(X) * Sin(Z) + Sin(X) * Sin(Y) * Cos(Z);..
svick

답변:


14

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

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


... 내가 왜 그 :) 생각하지 않았다
9a3eedi

충분히 간단한 크로스 플랫폼 솔루션이기 때문에 이것을 정답으로 표시했습니다. 다른 솔루션도 정확합니다. 나는 이것을 생각하지 않았다는 것을 정말로 믿을 수 없다 :) 그것은 너무 명백하다
9a3eedi

31

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.*;모든 어설 션을 사용할 수 있는 경우가 종종 있습니다.


훌륭한 답변입니다. 이 기능을 몰랐습니다. 이것이 가능한 Java 버전은 무엇입니까?
9a3eedi

@ 9a3eedi 처음으로 Java 1.5에서 사용 가능합니다.

좋은 기술. 나는 그것을 좋아한다. +1.
랜달 쿡

1
@RandallCook Java 1.4 일에 사람들은 인터페이스의 상수에 액세스하기 위해 모든 클래스에서 public interface Constants { final static public double PI = 3.14; }다음 과 같은 작업을 수행 public class Foo implements Constants합니다. 이것은 혼란을 초래했습니다. 따라서 1.5를 사용하면 인터페이스를 구현하지 않고도 특정 상수와 정적 함수를 가져올 수 있도록 정적 가져 오기가 추가되었습니다.

3
특정 기능을 선택적으로 가져올 수 있습니다import static java.lang.Math.cos;
래칫 괴물

5

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;

4

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


8
예, 일반적으로 정의상 DSL은 특정 도메인에서 작업 할 때 유용 할 수 있습니다. 그러나이 DSL이 존재하지 않거나 필요에 맞지 않으면 유지 관리 해야합니다. 문제가 될 수 있습니다. 또한 특정 질문 ( "매번 Math 클래스를 작성하지 않고 어떻게 sin, cos,… 메소드 / 함수를 사용할 수 있습니까?")의 경우 DSL은 너무 큰 솔루션 일 수 있습니다.
mgoeminne

4

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

1
덧붙여서, 이것은 확장 속성에 대한 좋은 사용 사례가 될 것이고, 속성을 메소드로 남용하는 합법적 인 사용 사례가 될 것입니다!
Jörg W Mittag

이것이 내가 생각한 것입니다. x.Sin()약간의 조정이 필요하지만 확장 방법을 남용하고 이것은 개인적으로 나의 첫 성향 일 것입니다.
WernerCD

2

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

1

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

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

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


2
Scala는 CLR에서 사용할 수 없으며 JVM에서만 사용할 수 있습니다. 따라서 C #에 대한 실질적인 대안은 아닙니다.
ben rudgers

@benrudgers-C #은 JVM에서 실행되지 않으므로 실제로 Java에 대한 실질적인 대안은 아닙니다. 이 질문은 CLR이어야한다고 명시하지 않았습니다!
Rex Kerr

어쩌면 나는 Luddite이지만 "*"대신 "dot"에 대한 두 개의 추가 문자가 코드가 더 명확하다는 이점이 있지만 지불해야 할 작은 가격 인 것 같습니다. 그래도 좋은 대답입니다.
user949300
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.