如何提高C#,Java和类似语言中面向数学的代码的可读性?[关闭]


16

作为C程序员和C#程序员,我不喜欢C#的一件事就是数学函数的详细程度。例如,每次您必须使用正弦,余弦或幂函数时,都必须在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程序员是否使用任何技巧来提高Math代码的可读性。但是我意识到C#/ Java / etc。不是像MATLAB之类的面向数学的语言,所以这很有意义。但是有时候,人们仍然需要编写数学代码,如果可以使其更具可读性,那就太好了。


我不具体知道,但是您可能会找到一个代数库,该库将允许您使用字符串定义数学函数,尽管会降低性能。
raptortech97


7
您担心有些冗长,但一元运算符在'*'中愉快地隐藏了'+'-都没有大括号-我怀疑您的优先级错了。
mattnz

1
这只是一个例子,但很不错
9a3eedi 2014年

6
在C#6.0中,您将能够编写:using System.Math; … double x = -Cos(X) * Sin(Z) + Sin(X) * Sin(Y) * Cos(Z);
svick 2014年

Answers:


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

您还可以创建将常用数学运算捆绑为单个运算的函数。这样可以最大程度地减少函数在代码中出现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)
}

您正在按点和旋转的级别进行工作,并且底层的trig函数被掩埋。


...为什么我没有想到这一点:)
9a3eedi 2014年

我将其标记为正确答案,因为它是一个非常简单的跨平台解决方案。其他解决方案也是正确的。我真的不敢相信我没有想到这个:)这太明显了
9a3eedi 2014年

31

在Java中,有许多工具可以使某些事情变得不那么冗长,您只需要了解它们即可。在这种情况下有用的是static导入(教程页面维基百科)。

在这种情况下,

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 CTP于2015年1月发布以来,静态导入需要显式关键字static。喜欢:

using static System.Console;

4

除了这里的其他好答案之外,我可能还会推荐DSL为具有大量数学复杂性的情况(不是一般用例,但可能是一些财务或学术项目)。

使用DSL生成工具(例如Xtext),您可以定义自己的简化数学语法,从而可以生成一个类,其中包含公式的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 2014年

4

在C#中,您可以使用扩展方法。

一旦习惯了“后缀”符号,下面的内容将非常漂亮:

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
顺便说一句,这对于扩展属性将是一个很好的用例,甚至对于滥用属性作为方法也将是一个合法的用例!
约尔格W¯¯米塔格

这就是我的想法。x.Sin()会需要一些调整,但是我滥用扩展方法,这将是我个人的第一个倾向。
WernerCD 2014年

2

C#:Randall Cook的答案的变体我喜欢,因为它比扩展方法更能保持代码的数学“外观”,我喜欢这样,它是使用包装器,但对调用使用函数引用,而不是包装它们。我个人认为这会使代码看起来更简洁,但是基本上它是在做相同的事情。

我敲了一些LINQPad测试程序,包括Randall的包装函数,我的函数引用和直接调用。

函数引用的调用基本上与直接调用花费相同的时间。打包的函数始终较慢-尽管数量不多。

这是代码:

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!您可以定义符号运算符,并且不需要方法的括号。这使数学方法更易于解释。

例如,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,2014年

@benrudgers-C#不能在JVM上运行,因此它并不是Java的可行替代品,Java也是一个问题。问题没有指定必须是CLR!
Rex Kerr 2014年

也许我是Luddite,但是用两个额外的字符代替“ *”而不是“ *”,好处是代码更清晰,似乎付出的代价很小。仍然是一个很好的答案。
user949300
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.