Comme un programmeur C et un programmeur C #, une des choses que je n'aime pas chez C # est la manière dont les fonctions de mathématiques verbeuses sont. Chaque fois que vous devriez utiliser un péché, un cosinus ou une fonction de puissance, par exemple, vous devez préparer la classe statique en mathématiques. Cela conduit à un code très long lorsque l'équation elle-même est assez simple. Le problème devient encore pire si vous avez besoin de taper des types de données. En conséquence, à mon avis, la lisibilité 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 aussi le cas dans d'autres langueurs comme Java.
Je ne sais pas si cette question ait une solution, mais j'aimerais savoir s'il y a des astuces C # ou Java Les programmeurs utilisent pour améliorer la lisibilité du code mathématique. Je réalise cependant que c #/Java/etc. Ne sont pas des langues orientées mathématiques comme Matlab ou similaire, donc cela a du sens. Mais parfois, il aurait toujours besoin d'écrire du code de mathématiques et que ce sera génial si on pouvait le rendre plus lisible.
Vous pouvez définir des fonctions locales qui appellent les fonctions statiques globales. Espérons que le compilateur fera appel aux emballages, 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 forment des opérations mathématiques communes en opérations simples. Cela minimiserait le nombre d'instances dans lesquelles des fonctions telles que sin
et cos
apparaissent dans votre code, rendant ainsi la clunnkiness d'invoquer les fonctions statiques globales moins perceptibles. 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 de Trig sous-jacentes sont enterrées.
Au sein de Java, de nombreux outils sont disponibles pour rendre certaines choses moins de verbose, il vous suffit de les connaître. Ceci est utile dans ce cas est celui de l'importation static
( page de 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 assez bien ( Ideone ). Il est un peu lourd pour faire une importation statique de tout de la classe de mathématiques, mais si vous faites beaucoup de mathématiques, cela pourrait être appelé pour.
L'importation statique vous permet d'importer un champ statique ou une méthode dans l'espace de noms de cette classe et l'invoquer sans nécessiter le nom du package. Vous trouvez souvent cela dans les cas de test Junit où import static org.junit.Assert.*;
se trouve pour obtenir tous les ASSERTS disponibles.
avec C # 6.0 Vous pouvez utiliser la fonctionnalité de l'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: statique à l'aide de déclarations (aperçu de la langue C # 6.0)
Une autre caractéristique du sucre syntaxique C # 6.0 est l'introduction de l'utilisation statique. Avec cette fonctionnalité, il est possible de éliminer une référence explicite au type lors de l'appel d'une méthode statique. En outre, l'utilisation de statiques 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, le CTP publié en janvier 2015, l'importation statique nécessite un mot clé explicite static
. Comme:
using static System.Console;
En plus des autres bonnes réponses ici, je pourrais également recommander un [~ # ~ # ~] dsl [~ # ~] Pour des situations avec une complexité mathématique substantielle (pas des cas d'utilisation moyenne, mais peut-être de certains financiers ou universitaires projets).
Avec un outil de génération DSL telle que xtext , vous pouvez définir votre propre grammaire mathématique simplifiée, qui pourrait à son tour générer une classe avec contenant le Java (ou toute autre langue ) Représentation 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 du plug-in de la grammaire et de l'éclipse ne valaient pas la peine, mais pour des projets plus compliqués, cela pourrait générer de grands avantages, en particulier si la DSL permettrait aux gens d'affaires ou aux chercheurs académiques de maintenir des documents formels dans un confortable Langue, et soyez assuré que leur travail a été traduit avec précision dans la langue de mise en œuvre du projet.
En C #, vous pouvez utiliser des méthodes d'extension.
Le ci-dessous lit assez joliment 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 précédente de l'opérateur rend les choses un peu plus laids lorsqu'il s'agit de chiffres négatifs ici. Si vous souhaitez calculer Math.Cos(-X)
au lieu de -Math.Cos(X)
Vous devrez enfermer le nombre de parenthèses:
var x = (-X).Cos() ...
C #: une variante sur Randall Cook's Réponse , que j'aime parce qu'elle conserve le "look" mathématique du code plus que des méthodes d'extension, est d'utiliser une emballage mais d'utiliser des références de fonction pour les appels plutôt que d'envelopper eux. Personnellement, je pense que le code a l'air plus propre, mais cela fait essentiellement la même chose.
J'ai assommé un petit programme de test Linqpad, y compris les fonctions emballées de Randall, mes références de fonction et les appels directs.
La fonction Les appels référencés prennent essentiellement la même heure que les appels directs. Les fonctions enveloppées sont toujours plus lentes - bien qu'elles ne soient pas énormes.
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
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 voies plus facile à interpréter.
Par exemple, le même calcul dans Scala et Java peut ê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 ajoute assez rapidement.