Que peut-on faire pour améliorer la lisibilité du code mathématique en C #, Java et similaire? [fermé]


16

En tant que programmeur C et programmeur C #, l'une des choses que je n'aime pas dans C # est la façon dont les fonctions mathématiques sont verbeuses. Chaque fois que vous devez utiliser une fonction Sin, cosinus ou power par exemple, vous devez ajouter la classe statique Math. Cela conduit à un code très long lorsque l'équation elle-même est assez simple. Le problème devient encore pire si vous devez taper des types de données. En conséquence, à mon avis, la lisibilité en souffre. Par exemple:

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

Par opposition à simplement

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

C'est également le cas dans d'autres langages comme Java.

Je ne sais pas si cette question a réellement une solution, mais j'aimerais savoir s'il existe des astuces que les programmeurs C # ou Java utilisent pour améliorer la lisibilité du code Math. Je me rends compte cependant que C # / Java / etc. ne sont pas des langages orientés mathématiques comme MATLAB ou similaire, donc cela a du sens. Mais parfois, il faudrait encore écrire du code mathématique et ce serait bien si on pouvait le rendre plus lisible.


Je n'en connais pas spécifiquement, mais vous pourriez probablement trouver une bibliothèque d'algèbre qui vous permettrait de définir des fonctions mathématiques avec des chaînes, bien qu'il y aurait une pénalité de performance.
raptortech97


7
Vous vous inquiétez d'un peu de verbosité supplémentaire, mais vous cachez heureusement un «+» parmi «*» avec des opérateurs unaires - tous sans accolades - je soupçonne que vous avez des priorités erronées.
mattnz

1
Ce n'était qu'un exemple, mais bon point
9a3eedi

6
En C # 6.0, vous serez en mesure d'écrire: using System.Math; … double x = -Cos(X) * Sin(Z) + Sin(X) * Sin(Y) * Cos(Z);.
svick

Réponses:


14

Vous pouvez définir des fonctions locales qui appellent les fonctions statiques globales. Espérons que le compilateur alignera les wrappers, puis le compilateur JIT produira un code d'assemblage serré pour les opérations réelles. Par exemple:

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

Vous pouvez également créer des fonctions qui regroupent des opérations mathématiques courantes en opérations simples. Cela minimiserait le nombre d'instances où des fonctions comme sinetcos apparaissent dans votre code, rendant ainsi la maladresse d'invoquer les fonctions statiques globales moins perceptible. Par exemple:

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

Vous travaillez au niveau des points et des rotations, et les fonctions trigonométriques sous-jacentes sont enterrées.


... pourquoi n'y ai-je pas pensé :)
9a3eedi

J'ai marqué cela comme la bonne réponse car c'est une solution multiplateforme qui est assez simple. Les autres solutions sont également correctes. Je ne peux vraiment pas croire que je n'y ai pas pensé :) c'est juste trop évident
9a3eedi

31

En Java, il existe de nombreux outils pour rendre certaines choses moins verbeuses, il suffit d'en être conscient. Celui qui est utile dans ce cas est celui de l' staticimport ( page tutoriel , wikipedia ).

Dans ce cas,

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

fonctionne très bien ( ideone ). C'est un peu lourd de faire une importation statique de toute la classe Math, mais si vous faites beaucoup de mathématiques, alors cela pourrait être nécessaire.

L'importation statique vous permet d'importer un champ ou une méthode statique dans l'espace de noms de cette classe et de l'appeler sans exiger le nom du package. Vous le trouverez souvent dans les cas de test Junit où l' import static org.junit.Assert.*;on trouve tous les assertions disponibles.


Excellente réponse. Je n'étais pas au courant de cette fonctionnalité. Sous quelle version de Java est-ce possible?
9a3eedi

@ 9a3eedi Il a d'abord été rendu disponible en Java 1.5.

Belle technique. Je l'aime. +1.
Randall Cook

1
@RandallCook En Java 1.4 jours, les gens faisaient des choses comme public interface Constants { final static public double PI = 3.14; }et puis public class Foo implements Constantsdans toutes les classes pour avoir accès aux constantes de l'interface. Cela a fait un gros gâchis. Ainsi, avec 1.5, l'importation statique a été ajoutée pour permettre d'extraire des constantes et des fonctions statiques spécifiques sans avoir à implémenter une interface.

3
Vous pouvez importer sélectivement certaines fonctionsimport static java.lang.Math.cos;
ratchet freak

5

Avec C # 6.0, vous pouvez utiliser la fonction d'importation statique.

Votre code pourrait être:

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

Voir: Static Using Statements (AC # 6.0 Language Preview)

Une autre fonctionnalité du «sucre syntaxique» C # 6.0 est l'introduction de l'utilisation de l'électricité statique. Avec cette fonctionnalité, il est possible d' éliminer une référence explicite au type lors de l'appel d'une méthode statique. De plus, l'utilisation de statique vous permet d'introduire uniquement les méthodes d'extension sur une classe spécifique, plutôt que toutes les méthodes d'extension dans un espace de noms.

EDIT: depuis Visual Studio 2015, CTP sorti en janvier 2015, l'importation statique nécessite un mot clé explicite static. comme:

using static System.Console;

4

En plus des autres bonnes réponses ici, je pourrais également recommander une DSL pour les situations avec une complexité mathématique substantielle (pas des cas d'utilisation moyens, mais peut-être certains projets financiers ou académiques).

Avec un outil de génération DSL tel que Xtext , vous pourriez définir votre propre grammaire mathématique simplifiée, qui pourrait à son tour générer une classe contenant la représentation Java (ou tout autre langage) de vos formules.

Expression DSL:

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

Sortie générée:

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

Dans un exemple aussi simple, les avantages de la création de la grammaire et du plug-in Eclipse ne seraient pas utiles, mais pour des projets plus complexes, cela pourrait générer de grands avantages, surtout si la DSL permettait aux gens d'affaires ou aux chercheurs universitaires de conserver des documents formels dans un cadre confortable. et soyez assuré que leur travail a été traduit avec précision dans la langue de mise en œuvre du projet.


8
Oui, en général et par définition, une DSL peut être utile lorsque vous travaillez dans un domaine spécifique. Cependant, si cette DSL n'existe pas, ou si elle ne correspond pas aux besoins, vous devez la maintenir , ce qui peut être problématique. De plus, pour la question spécifique ("Comment puis-je utiliser les méthodes / fonctions sin, cos,… sans écrire la classe Math à chaque fois"), un DSL est peut-être une solution surdimensionnée.
mgoeminne

4

En C #, vous pouvez utiliser des méthodes d'extension.

Ce qui suit se lit assez bien une fois que vous vous êtes habitué à la notation "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();

Malheureusement, la priorité des opérateurs rend les choses un peu plus laides lorsqu'il s'agit de nombres négatifs ici. Si vous voulez calculer Math.Cos(-X)au lieu de -Math.Cos(X)vous devrez mettre le nombre entre parenthèses:

var x = (-X).Cos() ...

1
Soit dit en passant, cela constituerait un bon cas d'utilisation pour les propriétés d'extension, et même un cas d'utilisation légitime pour abuser des propriétés en tant que méthodes!
Jörg W Mittag

Voilà ce que je pensais. x.Sin()prendrait un certain ajustement, mais j'abuse des méthodes d'extension et ce serait, personnellement, ma première inclination.
WernerCD

2

C #: Une variante de la réponse de Randall Cook , que j'aime parce qu'elle conserve plus l'aspect mathématique du code que les méthodes d'extension, consiste à utiliser un wrapper mais à utiliser des références de fonction pour les appels plutôt que de les envelopper. Personnellement, je pense que cela rend le code plus propre, mais il fait essentiellement la même chose.

J'ai créé un petit programme de test LINQPad comprenant les fonctions encapsulées de Randall, mes références de fonction et les appels directs.

La fonction des appels référencés prend essentiellement le même temps que les appels directs. Les fonctions encapsulées sont toujours plus lentes - mais pas énormément.

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

Résultats:

Function reference:
00:00:06.5952113

Wrapped function:
00:00:07.2570828

Direct call:
00:00:06.6396096

1

Utilisez Scala! Vous pouvez définir des opérateurs symboliques et vous n'avez pas besoin de parens pour vos méthodes. Cela rend les mathématiques façon plus facile à interpréter.

Par exemple, le même calcul dans Scala et Java pourrait être quelque chose comme:

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

Cela s'additionne assez rapidement.


2
Scala n'est pas disponible sur le CLR, seulement sur la JVM. Ce n'est donc pas vraiment une alternative viable au C #.
ben rudgers

@benrudgers - C # ne fonctionne pas sur la JVM, donc ce n'est pas vraiment une alternative viable à Java, sur laquelle la question a également été posée. La question ne précise pas que ce doit être CLR!
Rex Kerr

Je suis peut-être un luddite, mais deux caractères supplémentaires pour "dot" au lieu de "*", avec l'avantage que le code est plus clair, semble un petit prix à payer. Pourtant, une bonne réponse.
user949300
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.