16

As both a C programmer and a C# programmer, one of the things I don't like about C# is how verbose math functions are. Every time you would have to use a Sin, cosine, or power function for example, you'd have to prepend the Math static class. This leads to very long code when the equation itself is pretty simple. The problem becomes even worse if you need to typecast data types. As a result, in my opinion, the readability suffers. For example:

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

As opposed to simply

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

This is also the case in other langauges like Java.

I'm not sure if this question actually has a solution, but I would like to know if there are any tricks C# or Java programmers use to improve readability of Math code. I realize however that C#/Java/etc. are not math-oriented languages like MATLAB or similar, so it makes sense. But occasionally one would still need to write math code and it'll be great if one could make it more readable.

9a3eedi
  • 2,099

7 Answers7

31

Within Java, there are many tools available to make certain things less verbose, you just have to be aware of them. One that is useful in this case is that of the static import (tutorial page, wikipedia).

In this case,

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

runs quite nicely (ideone). It's a bit heavy handed to do a static import of all of the Math class, but if you are doing a lot of math, then it might be called for.

The static import allows you to import a static field or method into the namespace of this class and invoke it without requiring the package name. You often find this in Junit test cases where import static org.junit.Assert.*; is found to get all the asserts available.

14

You could define local functions that call the global static functions. Hopefully the compiler will inline the wrappers, and then the JIT compiler will produce tight assembly code for the actual operations. For example:

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

You could also create functions that bundle up common math operations into single operations. This would minimize the number of instances where functions like sin and cos appear in your code, thereby making the clunkiness of invoking the global static functions less noticeable. For example:

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

You are working at the level of points and rotations, and the underlying trig functions are buried.

Randall Cook
  • 2,480
5

With C# 6.0 you can use the feature of Static Import.

Your code could be:

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

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

Another C# 6.0 “syntactic sugar” feature is the introduction of using static. With this feature, it’s possible to eliminate an explicit reference to the type when invoking a static method. Furthermore, using static lets you introduce only the extension methods on a specific class, rather than all extension methods within a namespace.

EDIT: Since Visual Studio 2015, CTP released in January 2015, static import requires explicit keyword static. like:

using static System.Console;
Habib
  • 151
4

In addition to the other good answers here, I might also recommend a DSL for situations with substantial mathematical complexity (not average use cases, but perhaps some financial or academic projects).

With a DSL generation tool such as Xtext, you could define your own simplified mathematical grammar, which could in turn generate a class with containing the Java (or any other language) representation of your formulae.

DSL Expression:

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

Generated Output:

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

In such a simple example, the benefits of creating the grammar and Eclipse plugin wouldn't be worthwhile, but for more complicated projects, it could yield great benefits, especially if the DSL enabled business folks or academic researchers to maintain formal documents in a comfortable language, and be assured that their work was translated accurately into the project's implementation language.

Dan1701
  • 3,108
4

In C# you could use extension methods.

The below reads pretty nicely once you get used to the "postfix" notation:

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

Unfortunately operator precedence makes things a bit uglier when dealing with negative numbers here. If you want to calculate Math.Cos(-X) instead of -Math.Cos(X) you will need to enclose the number in parenthesis:

var x = (-X).Cos() ...
rjnilsson
  • 409
2

C#: A variation on Randall Cook's answer, which I like because it maintains the mathematical "look" of the code more than extension methods, is to use a wrapper but use function references for the calls rather than wrap them. I personally think it makes the code look cleaner, but it is basically doing the same thing.

I knocked up a little LINQPad test program including Randall's wrapped functions, my function references, and the direct calls.

The function referenced calls basically take the same time as the direct calls. The wrapped functions are consistently slower - although not by a huge amount.

Here's the code:

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

Results:

Function reference:
00:00:06.5952113

Wrapped function:
00:00:07.2570828

Direct call:
00:00:06.6396096
1

Use Scala! You can define symbolic operators, and you don't need parens for your methods. This makes math way easier to interpret.

For example, the same calculation in Scala and Java could be something like:

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

This adds up pretty quickly.

Rex Kerr
  • 1,234