AUSTRA arithmetic is basically the same as on most programming languages. The language supports:
32 bits integers, represented by the int type.
64 bits integers, represented by the long type.
64 bits double-precision reals, represented by the double type.
2x64 bits double-precision complex values, represented by the complex type.
Smaller arithmetic types are automatically converted to bigger types when required: int to double, double to complex, and even int to complex. Double and long integer values can be converted into integer values using the toInt property, as in pi.toInt.
These are the operators available for integers and reals:
+ | Addition. Can also be used as a unary operator. |
- | Subtraction. Can also be used as a unary operator for negation. |
* | Multiplication. |
/ | Both real and integer division. |
% | Both integer and real remainders. |
^ | Power: 2^3 = 8, 9^0.5 = 3. |
Most of them may also be used with complex numbers.
Though the power operator works both for integer, real and complex numbers, the compiler optimizes the special cases when the power is 2, 3 and 4, so equalities like this exactly holds:
i^2 = -1
The multiplication operator can be elided when the first operand is a real or an integer and it is immediately followed by an identifier:
2pi = 2 * pi
2x^2 + 3x + 1 = 2*x^2 + 3*x + 1
1/2x = 1 / (2*x)
AUSTRA also recognizes a superscript 2 (²) as an operator to square a value:
2x² + 3x + 1 = 2*x^2 + 3*x + 1
The AUSTRA code editor simplifies typing this operator with the keys combination (Ctrl+G, 2).
These operators are used for comparing all compatible operands:
= | Equality. |
!= | Inequality. |
<> | A synonym for the inequality operator. |
< | Less than. |
<= | Less than or equal to. |
> | Greater than. |
>= | Greater than or equal to. |
<- | Belongs to. Right side must be a vector or a sequence. |
∈ | A fancy synonym for the membership operator (<-). |
The membership operator can be used with sequences, vectors, matrices, and series:
34 <- [1..100];
0 <- vec:: random(1024)
When the right side of the membership operator is a time series, the left operand may be either a real or a date:
0.0 <- appl.rets;
1@jan2020 <- appl
Comparisons can be fused for numeric operands using the following syntax:
sqrt(pi) <= pi <= pi²
Fused ranges only require combining same-direction comparisons. For instance, <= and < are compatible, but < and > are not.
When you have a complex value in your hands, you can drill into it using a dot and a property name, to extract information about the poor little value:
real | The real part of the complex. |
imaginary | The imaginary part of the complex. |
magnitude | A magnitude, i.e., the distance to complex(0, 0). |
phase | The phase, in radians. |
let c = complex(3, 4) in
c = c.real + c.imaginary * i
If typing magnitude is too hard for your nerves, you can use mag as an accepted synonym. real can be shortened to re, and imag and even im can be used instead of imaginary. Since my heart is cold and empty for phase, on the other hand, there is no diminutive for that fellow.
In addition to the usual operators, there's a sufix operator for conjugating a complex value:
' | Unary suffix operator for complex conjugation. |
The ' operator is also used for conjugating complex vectors and transposing a matrix.
Integer values support the even and odd properties for easy testing of parity:
iff(e.toInt.even, "Truncated to 2", "Rounded to 3")
The math class groups methods and properties dealing with arithmetic operations. Most of these features come straight from the C#'s Math and Complex, but it also incorporate other functions that are used in statistics and probabilities.
Our math is special in that the class prefix is assumed when not present in a function or property call:
-- Write like this, if you are a sucker for pain.
math:: sin(math:: pi/4) = math:: sqrt(2)/2;
-- Standard people use this style.
sin(pi/4) = sqrt(2)/2
Why, for the love of Mike, have we sunken all those definition inside the math class? It is easy to explain with two points: we did not want to pollute the global name space with lots and lots of symbols, in the first place. Some mathematically-oriented language do just this: everything is a global function, so, at some point, you must come with very clever but cryptic names for your own stuff. Nonetheless, we can omit the class name for the most used names. The second point is related: somebody can shadow one of these global names, such i or max. In those cases, you still have the long and winding road of prefixing the shadowed name with its class name, and nothing is lost.
These are the methods or functions provided by this class. Most of them work both with integer, real and complex parameters:
abs | Absolute value |
acos | The angle whose cosine is the specified parameter. |
asin | The angle whose sine is the specified parameter. |
atan(x), atan(x, y) | The angle whose tangent is the specified parameter. The version with two parameters is equivalent to Atan2. |
beta(x, y) | Biparametric Euler integral of the first kind. |
cbrt | Cubic root. |
complex | Creates a complex number from one or two real values. |
cos | The cosine function. |
cosh | The hyperbolic cosine function. |
erf | The error function. |
exp | The exponential function. |
gamma | The gamma function: an extension of factorials for real numbers. |
lnGamma | The natural logarithm of the gamma function. |
log | The natural logarithm function. |
log10 | Base 10 logarithms. |
max | The maximum of its two parameters. It also works with dates. |
min | The minimum of its two parameters. It also works with dates. |
ncdf | Normal cumulative distribution function. |
polar | Creates a complex from its circular coordinates. |
probit | The inverse of the cumulative of the standard normal distribution. |
round(d) | Rounds a real to the nearest integer. |
round(d, i) | Rounds a real to a number of decimals. |
sign | Returns the sign of the argument. |
sin | The sine function. |
sinh | The hyperbolic sine function. |
sqrt | The square root. |
tan | The tangent function. |
tanh | The hyperbolic tangent function. |
trunc | Truncates a real value. |
These are the properties (parameterless functions) and constants provided by math:
e | Euler's constant. |
i | The imaginary unit. |
maxInt | The maximum value that is representable in an integer. |
maxReal | The maximum value that is representable in a real. |
minInt | The minimum value that is representable in an integer. |
minReal | The minimum value that is representable in a real. |
nrandom | A random number from the standard normal distribution. |
pearl | An Easter Egg. Just try me! |
pi, π | Don't be irrational: be trascendent. |
random | A random number from a uniform distribution between 0 and 1. |
tau, τ | Twice π. |
today | The current date. |
These methods are also defined inside the math class, so they can be used without explicitly writing the class prefix:
solve | A simple Newton-Raphson solver. See below for details. |
polyEval | Evaluates a polynomial given a real or complex argument. |
polyDerivative | Evaluates the first derivative of a polynomial at a real or complex argument. |
polySolve | Calculates all the roots of a polynomial. |
The Newton-Raphson solver is a function accepting from three up to five arguments:
solve(x => sin(x) - 1, x => cos(x), 0, 1e-9, 100)
The first two arguments are lambda functions: one for the function we want to solve for a root, and the second for the first derivative of that function. Please note that solve does not verify that the lambda function and its derivative lambda match. The third argument is required, and represents the initial guess to start running the algorithm. Again, a bad guess may make the algorithm fail.
The fourth and fifth arguments can be omitted. The fourth parameter is the desired accuracy, and when omitted, it defaults to 1e-9. The last parameter is the maximum numbers of iterations, which by default is 100.
The polyEval function takes either a complex or a real as its first argument, and a list of coefficients, either in a single vector or as a list of real values, and evaluates the polynomial at the supplied value:
let x1 = complex(-1, sqrt(2)), x2 = x1';
-- 1, 2, 3 represents the polynomial x² + 2x + 3
polyEval(x1, 1, 2, 3);
-- Coefficients can be grouped in a vector.
polyEval(x2, [1, 2, 3]);
The inverse of polyEval is the polySolve function. It takes either a vector or a list of reals and considers them as the coefficients of a polynomial. The first value is the coefficient of the highest degree term. For instance, the vector [1, 2, 3, 4] stands for the polynomial x^3 + 2*x^2 + 3*x + 4. This function can throw an exception if it does not know how to handle a given polynomial, or when there are no available roots. The returned value is always a complex vector, even when all roots are real-valued. For instance:
polySolve(1, 2, 3)
You can check the accuracy of the answers from the solver using this trick:
let poly = [1, 2, 3] in
polySolve(poly).all(c => abs(polyEval(c, poly)) <= 1e-15)
Of course, the accuracy of the roots may vary according to the polynomial.
A close relative of polyEval is polyDerivative, which calculates the derivative of a given polynomial at the specified argument:
let v = [1, 2, 3, 4];
polyEval(2, v) = 26;
polyDerivative(2, v) = 23
polyDerivative can be useful when finding a real root for a polynomial using the Newton-Raphson algorithm.
Dates in Austra are represented by the date type and stores the number of days since Jan 1st, 1900. Dates support these properties:
day | Gets the day of month, starting by 1. |
dow | Gets the day of the week. |
isLeap | Is the year from the date a leap one? |
month | Gets the month of the date, starting with 1. |
toInt | Converts the date to a signed integer. |
year | Gets the year of the date. |
These two methods allow adding either a positive or a negative number of months or years to a date:
addMonths | Adds a positive or negative number of months to a date. |
addYears | Adds a positive or negative number of years to a date. |
Adding or subtracting days from a date is achieved with these operators:
+ | Adds a number of days to a date. The left operand must be a date. |
- | Subtracts a number of days from a date. The left operand must be a date. It can also be used to find the difference in days between two dates. |
Logical values are represented by the bool data type. Variables and parameters from this type hold one of these two constants: either false or true.
Operators acting on logical values resembles more the good-old Pascal operators than the C/C++/C# one. It's a matter of personal preference, of course, but also of readability:
not | Logical negation. |
and | Logical conjunction. |
or | Logical disjunction. |
The precedence of these operators is the standard one. Negation binds first, then conjunction, and finally disjunction.
Since AUSTRA is a functional language, it doesn't have "statements". However, it provides an if/then/else ternary operator, equivalent to the also included iff() function:
if aapl.mean < msft.mean then aapl.mean else msft.mean
Of course, the above expression is just a pedantic way to write min(aapl.mean, msft.mean). It can be also be expressed using iff() this way:
iff(aapl.mean < msft.mean, aapl.mean, msft.mean)
Most of the times, the more verbose ternary operator is easier to read. The ternary operator has another advantage: you can chain several conditions and responses using the elif keyword.
let x = random;
if x < 0.1 then "Too low!"
elif x < 0.5 then "A little low"
elif x < 0.9 then "A little high"
else "Too high!"