<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta>
      <journal-title-group>
        <journal-title>Moscow, Russian, April</journal-title>
      </journal-title-group>
    </journal-meta>
    <article-meta>
      <title-group>
        <article-title>Approaches to the implementation of generalized complex numbers in the Julia language</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Migran N. Gevorkyan</string-name>
          <email>gevorkyan-mn@rudn.ru</email>
          <xref ref-type="aff" rid="aff0">0</xref>
          <xref ref-type="aff" rid="aff1">1</xref>
          <xref ref-type="aff" rid="aff3">3</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Anna V. Korolkova</string-name>
          <email>korolkova-av@rudn.ru</email>
          <xref ref-type="aff" rid="aff0">0</xref>
          <xref ref-type="aff" rid="aff1">1</xref>
          <xref ref-type="aff" rid="aff3">3</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Dmitry S. Kulyabov</string-name>
          <email>kulyabov-ds@rudn.ru</email>
          <xref ref-type="aff" rid="aff0">0</xref>
          <xref ref-type="aff" rid="aff1">1</xref>
          <xref ref-type="aff" rid="aff2">2</xref>
          <xref ref-type="aff" rid="aff3">3</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>(D. S. Kulyabov)</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
          <xref ref-type="aff" rid="aff3">3</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>6</institution>
          ,
          <addr-line>Miklukho-Maklaya St., Moscow, 117198</addr-line>
          ,
          <country country="RU">Russia</country>
        </aff>
        <aff id="aff1">
          <label>1</label>
          <institution>Department of Applied Probability and Informatics, Peoples' Friendship University of Russia, RUDN University</institution>
        </aff>
        <aff id="aff2">
          <label>2</label>
          <institution>Laboratory of Information Technologies, Joint Institute for Nuclear Research</institution>
          ,
          <addr-line>6, Joliot-Curie St., Dubna, Moscow</addr-line>
        </aff>
        <aff id="aff3">
          <label>3</label>
          <institution>region</institution>
          ,
          <addr-line>141980</addr-line>
          ,
          <country country="RU">Russia</country>
        </aff>
      </contrib-group>
      <pub-date>
        <year>2020</year>
      </pub-date>
      <volume>1</volume>
      <fpage>3</fpage>
      <lpage>17</lpage>
      <abstract>
        <p>anism. In problems of mathematical physics in order to study the structures of spaces by using the Cayley-Klein models in theoretical calculations, the generalized complex numbers are essential. In the case of computational experiments, such tasks require their high-quality implementation in a programming language. The proposed small deployment of generalized complex numbers in modern programming languages have several disadvantages. In this article we propose to use the Julia language as the language for generalized complex numbers implemention, not least because it supports the multiple dispatch mechThe paper demonstrates the approach to the implementation of one of the types of generalized complex numbers, namely dual numbers. We place particular emphasis on the description of the use of the multiple dispatch mechanism to introduce a new numerical type. The resulting implementation of dual numbers can be considered as a prototype for a complete software module supporting generalized complex numbers.</p>
      </abstract>
      <kwd-group>
        <kwd>complex numbers</kwd>
        <kwd>parabolic complex numbers</kwd>
        <kwd>dual numbers</kwd>
        <kwd>multiple dispatch</kwd>
        <kwd>Julia</kwd>
      </kwd-group>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <p>
        There is the approach that allows one to generalize the complex numbers and get three diferent
classes of generalized complex numbers [
        <xref ref-type="bibr" rid="ref1 ref2 ref3 ref4">1, 2, 3, 4</xref>
        ]. In this approach, we set square equation
with a determinant
Depending on the sign of the determinant, one gets the following results:
Workshop on information technology and scientific computing in the framework of the X International Conference
      </p>
      <p>CEUR
Workshop
Proceedings
htp:/ceur-ws.org
IS N1613-073</p>
      <p>CEUR Workshop Proceedings (CEUR-WS.org)
2
+</p>
      <p>+  = 0.
Δ = 
2</p>
      <p>− 4.
• Δ &lt; 0—elliptic (normal complex numbers);
• Δ = 0—parabolic (dual complex numbers);
• Δ &gt; 0—hyperbolic (split complex numbers or double numbers).</p>
      <p>
        Initially, these systems of complex numbers were introduced to describe Cayley–Klein
models [
        <xref ref-type="bibr" rid="ref5 ref6">5, 6</xref>
        ]. However, they have other appliances. In particular, dual complex numbers may be
used for problems of automatic diferentiation [
        <xref ref-type="bibr" rid="ref7">7</xref>
        ].
      </p>
      <p>The goal of this work is to create a pure implementation of dual numbers that is as close as
possible to their mathematical definition. We use Julia programming language for this task.</p>
      <sec id="sec-1-1">
        <title>1.1. Article structure</title>
        <p>The 2 section provides suficient description of dual complex numbers. We use constructive
definition, afecting only operations with dual complex numbers. We try to avoid unnecessary
mathematical abstractions. In the section 3 we give a general idea of the multiple dispatch
mechanism — the fundamental idea of Julia language. Also, the multiple dispatch is the basis
for implementation of any user-defined type in Julia. In the section 4 we describe in detail our
implementation of dual complex numbers in Julia.</p>
      </sec>
      <sec id="sec-1-2">
        <title>1.2. Notations and conventions</title>
        <p>To denote a dual complex unit, we will use the symbol ε.</p>
      </sec>
    </sec>
    <sec id="sec-2">
      <title>2. Dual numbers</title>
      <p>The dual number  is defined algebraically as follows:
 =  + ε, ,</p>
      <p>∈ ℝ, ε2 = 0, ε ≠ 0.</p>
      <p>The value of  will be called the real part, and  — imaginary. There is no confusion in the
terminology, because in this article we not use the ordinary complex numbers.</p>
      <sec id="sec-2-1">
        <title>2.1. The algebraic form</title>
        <p>Algebraic properties for two numbers  1 =  1 + ε 1 and  2 =  2 + ε 2.</p>
        <p>Addition  1 +  2 = ( 1 +  2) + ε( 1 +  2).</p>
        <p>Subtraction  1 −  2 = ( 1 −  2) + ε( 1 −  2).</p>
        <p>Multiplication  1 ⋅  2 = ( 1 + ε 1) ⋅ ( 2 + ε 2) =  1 2 + ε( 1 2 +  1 2).</p>
        <p>Conjunction  ̄ =  − ε ,  ̄ = ( + ε ) ⋅ ( − ε ) =  2.</p>
        <p>Absolute value | | =  . The modulus of a dual number can be negative, and it can also be
calculated by the following formula:
Devision The division of two dual numbers  1 and  2 is defined for all  2 such that | 2| ≠ 0:
contribute to the real part of the result.
value. Such numbers have the following property:</p>
        <p>Consider a dual number of the form  ε, where  ∈ ℝ. This is a dual number with zero absolute
( ε) ⋅ ( ε) =  ε2 = 0,
these numbers is 0. Such numbers are called zero divisors.
from which it follows that for any number  ε there is a number  ε such that the product of</p>
      </sec>
      <sec id="sec-2-2">
        <title>2.2. Trigonometric form</title>
        <p>A dual number  such that | | ≠ 0 can be written as follows:
 + ε =  (1 +</p>
        <p>) =  (1 +  ε),

 ε
where the values 
number  , respectively.</p>
        <p>This form of a dual number is some analog of the trigonometric form of an ordinary complex
number and we will continue to call it trigonometric form of the dual number.</p>
        <p>For the conjugate number  ̄ , the trigonometric form is:
= | | =  and ε =</p>
        <p>=  / are called module and argument of the dual
 ̄ =  (1 −  ε),
the trigonometric form.</p>
        <p>In this case, | ̄ | = | | is an absolute value, and 
̄ = − / is an argument of a dual number in</p>
        <p>The analogy with the trigonometric form of a complex number continues further when
considering multiplication and division. When multiplying two dual numbers  1 and  2, we get
 =  1(1 +  1ε) ⋅  2(1 +  2ε) =  1 2(1 + ( 1 +  2)ε),
in other words, while multiplying, the arguments are added and the modules are multiplied.</p>
        <p>In the case of division, we get
 1
 2
=
 1(1 +  1ε1)
 2(1 +  2ε)
=
 1(1 +  1ε) 2(1 −  2ε)
 2(1 +  2ε) 2(1 −  2ε)
=
 1(1 + ( 1 −  2) )
 2(1 −  2ε +  2ε)
=
 1
 2
ε
(1 + ( 1 −  2)ε),
in other words, when dividing, arguments are subtracted, and modules are divided.</p>
        <p>Note that in the case of division, the number  2
must have a non-zero module | 2| ≠ 0.
simple:
which gives
In the trigonometric form, the operation of raising to the natural power of  looks especially
  = ( (1 +  ε)) =  ⋅</p>
        <p>⏟⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏟⏞⏞⏞⏞⏞⏞⋅⏞⏞⏞⏞⏟(1 + (
⋅ …
+</p>
        <p>+ …
⏟⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏟⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏞⏟

+  ) )=   (1 +  ε) =   + ε  −1.</p>
        <p>For finding 
√
 = √ (1 +  ε),  ∈ ℕ, assume that √</p>
        <p>=  1, then by definition  1 =  , hence
 1 (1 +  1ε) =  =  (1 +  ε),
√
 1 =   ,  1 =
,
√

 =</p>
        <p>√


 (1 +

 ε .</p>
        <p>)
√

 +  ε =   +
√</p>
        <p>1−
  ε
Note that for an odd  , the root √</p>
        <p>always exists, and for an even  — only for the number 
with a non-negative module | | =  ≠ 0. In the algebraic form, the formula for the root is:
 !</p>
      </sec>
      <sec id="sec-2-3">
        <title>2.3. Matrix form</title>
        <p>All algebraic operations on dual numbers can be reduced to matrix operations if we consider:
ε ↔
(0 0)  =  +  ε ↔</p>
        <p>(0  )</p>
        <p>Then, for example:
 1 ⋅  2 ↔</p>
        <p>=
 1  1
 2  2</p>
        <p>1 2  1 2 +  2 1
( 0  1) ( 0  2)
( 0
 1 2
)
↔  1 2 + ( 1 2 +  2 1) .</p>
        <p>ε</p>
        <p>If the programming language supports vectorization, then in theory the matrix form of dual
numbers can give you a performance gain. However, the efect is unlikely to be significant in
practice.</p>
      </sec>
      <sec id="sec-2-4">
        <title>2.4. Taylor series expansion</title>
        <p>We can use ε2 = ε3 = … = ε = 0 ∀ ∈ ℕ, to get:
exp( ε) = ∑
∞ ( ε)
 =0  !
= 1 +  ε +  2ε2
2!</p>
        <p>+ … = 1 +  ε,
exp( +  ε) =     ε
=   (1 +  ε).</p>
        <p>The more general formula is derived from the Taylor series for the function  ( ) at the point
=  ( )+ ′( ) ε+ 
As a result, we get the extremely important equation:</p>
        <p>( + ε ) =  ( ) +  ′( ) ε,
value of the first derivative at the point 
which gives a way to calculate the values of functions from a dual number, if the value of the
derivative  ′</p>
        <p>( ) is known. On the other hand, the same formula allows one to calculate the</p>
      </sec>
      <sec id="sec-2-5">
        <title>2.5. Elementary functions of dual numbers</title>
        <p>Here is a brief summary of basic elementary functions for illustration.
from the real number  .</p>
        <p>The formula  (</p>
        <p>+ ε ) =  ( ) +  ′( ) ε allows you to extend elementary functions to the set of
dual numbers, since the right part of the formula contains only the values of the function</p>
        <sec id="sec-2-5-1">
          <title>Trigonometric function</title>
          <p>sin( + ε ) = sin  +  ε cos 
cos( + ε ) = cos  −  ε sin 
tan( + ε ) = tan  +  ε/ cos2 
cot( + ε ) = cot  −  ε/ sin2 
Power functions
( + ε ) =   +   −1 ε
√</p>
          <p>√
 + ε =   (1 +

 ε
)</p>
        </sec>
        <sec id="sec-2-5-2">
          <title>Inverse trigonometric functions</title>
          <p>arcsin( + ε ) = arcsin  +  ε/√1 −  2
arccos( + ε ) = arccos  −  ε/√1 −  2
arctan( + ε ) = arctan  +  ε/(1 +  2)
arccot( + ε ) = arctan  −  ε/(1 +  2)</p>
        </sec>
        <sec id="sec-2-5-3">
          <title>Logarithmic functions and exponent</title>
          <p>exp( + ε ) = exp{ } +  ε exp{ }
log ( + ε ) = log  +  ε/ ln 
numbers
2.6. Calculation of the first derivative of a real value function by using dual
The formula  ( +ε ) =  ( )+
′
( ) ε can also be used to calculate the value of the first derivative
of the real function
 ( )
at the point  , if the value of the function  (
+ ε )is known. From the
point of view of analytical calculations, this method of finding the derivative does not make
sense, since the value  (</p>
          <p>+ ε ) is obtained analytically from the same formula. However, it
provides a numerical method for obtaining the value of the first derivative using numerical
calculations without any additional error. This method of finding the derivative with help
of dual numbers is called automatic diferentiation . It also should mensioned that automatic
diferentiation using dual numbers is limited to first order derivatives.</p>
          <p>Let’s first look at a simple example of automatic diferentiation, and then discuss the general
implementation principle for programming languages.</p>
          <p>As an example, let’s find the derivative of the function  ( ) =  sin  :</p>
          <p>( + ε ) = ( + ε ) sin( + ε ).</p>
          <p>Knowing the value of sin( + ε ) = sin  +  ε cos  , it is easy to calculate  ( + ε ):
 ( + ε ) = ( + ε)(sin  +  ε cos  ) =  sin  + (sin  +  cos  ) ε.</p>
          <p>Comparing with the general formula  ( + ε ) =  ( ) +  ′( ) ε, we get the value  ′( ) =
sin  +  ε cos  .</p>
          <p>In general, to find  ′( ), it is enough to find the value of  ′( + ε ), take the imaginary part of
this dual number and divide it by  . In addition, since the choice of the number  is arbitrary,
we can choose it equal to 1 and get rid of the need to divide by  .</p>
          <p>To apply automatic diferentiation using dual numbers, we need the programming language
which allows user-defined types, as well as overloading arithmetic operations and elementary
functions for this type. This is especially easy for object-oriented languages and languages that
support function overloading or multiple dispatching.</p>
        </sec>
      </sec>
    </sec>
    <sec id="sec-3">
      <title>3. Dynamical dispatch in Julia language</title>
      <p>
        The language Julia [
        <xref ref-type="bibr" rid="ref8">8</xref>
        ] appeared relatively recently, but has already gained popularity as a
language for scientific computing. We will assume that readers are already familiar with this
language, and briefly focus only on the concept of multiple dispatching [
        <xref ref-type="bibr" rid="ref10 ref11 ref12 ref9">9, 10, 11, 12</xref>
        ], which is
the basis of the language, its understanding is essential for further presentation.
      </p>
      <p>Dynamic dispatch is a mechanism that allows to select the specific implementation of a
polymorphic function or operator from a set and call it in a specific case.</p>
      <p>Multiple dispatching is based on dynamic dispatching. In this case, the choice of exact
implementation of polymorphic function is made based on the type, number, and order of
the function’s arguments. This is the runtime polymorphic dispatch. In addition to the term
«multiple dispatching», the term multimethod is also used.</p>
      <p>The mechanism of multiple dispatch is similar to the mechanism of functions and operators
overload, implemented, for example, in the C++ language. Function overloading, however, is
performed exclusively at the compilation stage, whereas multiple dispatching must also work
at the program run-time (run-time polymorphism).</p>
      <p>Julia supports overloading functions at the compilation time if all data types used in the
function can be casted at the compilation stage (so called type stable functions). The JIT compiler
creates eficient implementations for each combination of argument types.</p>
      <p>If it is impossible to cast data types at the compilation stage (type unstable function), the
dynamic dispatching mechanism is enabled. The compiler will not be able to create a specialized
version, but will create a generic version that runs slowly.</p>
      <p>This approach allows one to combine the speed of a compiled language with strict static
typing with the flexibility of an interpreted language with dynamic typing.</p>
    </sec>
    <sec id="sec-4">
      <title>4. Dual numbers implementation</title>
      <sec id="sec-4-1">
        <title>4.1. Existing implementations of dual numbers in Julia</title>
        <p>
          The authors are familiar at least with two realizations of dual numbers in Julia language:
• type Dual in the module for automatic diferentiation ForwardDif [
          <xref ref-type="bibr" rid="ref7">7</xref>
          ];
• separate module DualNumbers [
          <xref ref-type="bibr" rid="ref13">13</xref>
          ], the development of which is frozen in favor of
ForwardDif.
        </p>
        <p>Legend:
Abstract type
Primitive type
Composite types</p>
        <p>Signed</p>
        <p>Dual
Integer</p>
        <p>Bool</p>
        <p>Number
Complex</p>
        <p>Real
Rational
Unsigned
Float16</p>
        <p>Float32</p>
        <p>Float64
Int8</p>
        <p>Int16</p>
        <p>Int32</p>
        <p>Int64 Int128 UInt8 UInt16 UInt32 UInt64 UInt128</p>
        <p>
          In this section we will describe our proposed implementation of dual numbers in the Julia
language. It is based on a built-in type of complex numbers Complex [
          <xref ref-type="bibr" rid="ref14">14</xref>
          ], and on the
DualNumbers module. The proposed implementation in this paper serves only as a example of
creating a custom data type for the Julia language.
        </p>
        <p>We put clarity of presentation at the first place, so many computational optimizations were
deliberately omitted in favor of a larger clarity. For example, in the DualNumbers module,
the dual number  =  + ε can have ordinary complex number components  and  . Also in
DualNumbers for implementing elementary functions  ( ) from dual numbers  a third-party
module is used, which defines diferentiation rules for functions from the real variable. This
allows to automate the definition of the  ( ) function by the formula  ( + ε ) =  ( ) +  ′( ) ε,
since it is not necessary to explicitly write out derivatives of  ′( ).</p>
        <p>In our implementation,  and  are only real values and elementary functions are defined
explicitly.</p>
      </sec>
      <sec id="sec-4-2">
        <title>4.2. Data structure</title>
        <p>The adding of any custom data type in Julia starts with the data structure defining. For dual
numbers we define the structure Dual:
struct Dual{T&lt;:Real} &lt;: Number
"real part"
x::T
"imaginary part"
y::T
end</p>
        <p>The structure contains only two fields: the real x and the imaginary y parts of the dual
number. Both fields in the structure must have the same parametric type T, which must be a
subtype of the abstract type Real, as indicated by the &lt;: operator. The Dual type itself is a
subtype of the abstract type Number. In this way, the dual numbers are embedded in an existing
hierarchy of types (see figure 1).</p>
        <p>Immediately after declaring a new structure, we can create variables of the Dual type using
the default constructors. We must specify both fields of the structure as arguments of the Dual
function:
z = Dual(1, 2) # the same type T of the arguments
Dual{Float64}(1, 2.2) # casting arguments to the same type</p>
        <p>If the argument type is the same, we don’t need to explicitly specify the T parameter. If the
arguments have diferent types, the parametric type must be specified. In our example, both
arguments are casted to the Float64 type.</p>
      </sec>
      <sec id="sec-4-3">
        <title>4.3. Overloading functions in the show example</title>
        <p>To print the values of variables of the Dual type we must to overload the showfunction. This
function is intended for formatted printing of data to standard output and to REPL. The write,
print, and println functions are used to output a minimal text representation of data. The
last two functions print data only to the standard output stream. For simple data, one can make
no diference between formatted and compact views and overload only show function. Then
the other functions will call show.</p>
        <p>In terms of multiple dispatch, overloading a function means adding a new method to the
function. To add a method to the show function, we define it as follows:
Base.show(io::IO, z::Dual) = show(io, z.x, " + ", z.y, "ε")
The short syntax for function definition is used here. The prefix Base. is needed, since the
standard methods of the show function are located in the Base module. Functions from this
module can be called directly without a prefix, but for overloading the Base prefix is essential.</p>
        <p>It is worth mention that our version of the show method is simplified, since it does not take
into account special cases, for example, the negative imaginary part.</p>
        <p>Next, we will add methods for many functions from Base, so that our Dual type becomes
suficiently functional.</p>
      </sec>
      <sec id="sec-4-4">
        <title>4.4. Additional constructors</title>
        <p>Let us create some additional constructors. For this we should overload the default constructor
Dual.</p>
        <p>In total we will set 5 additional constructors:
Dual(x::Real, y::Real)= Dual(promote(x, y)...) # 1
Dual{T}(x::Real) where {T&lt;:Real} = Dual{T}(x, 0) # 2
Dual(x::Real) = Dual(promote(x, 0)...) # 3
Dual{T}(z::Dual) where {T&lt;:Real} = Dual{T}(z.x, z.y) # 4
Dual(z::Dual) = Dual(promote(z.x, z.y)...) # 5</p>
        <p>The first constructor allows us not to specify the T parameter every time if the arguments
have diferent types. The promote function converts the type of arguments passed to it to a
common type and returns the result as a tuple. The Postfix operator ... unpacks the tuple and
passes its elements as arguments to the constructor function. The language core defines casting
rules for all subtypes of the abstract type Real, so now the constructor will work correctly for
any combination of arguments, as long as the T&lt;:Real is true. For example, the following code
will work correctly:</p>
        <p>Dual(1//2, π) # 0.5 + π*ε</p>
        <p>We passed a rational number (type Rational) and a built-in global constant (number  )
of the type Float64 to the constructor. After that, the type casting rule worked and both
arguments were converted to the more general type Float64.</p>
        <p>The second and third additional constructors allow one to omit the imaginary part if it is
equal to zero:
Dual{Float32}(1) # 1.0 + 0ε, 2 constructor
Dual(1//2) # 1//2 + 0ε, 3 constructor</p>
        <p>Constructor number 2 is a parametric function that is declared using the where construct.
The T parameter is a subtype of the abstract type Real. Constructor number 3 works similarly
to first constructor. The fourth and fifth constructors allow one to pass in an argument to the
constructor other dual number.</p>
        <p>For more convenience, we can also create a separate constant for the imaginary unit ε:
const ε = Dual(0, 1)</p>
        <p>After overloading arithmetic operations, this constant will allow us to create new dual
numbers using an expression as close as possible to their algebraic notation:
z = 1 + 2ε</p>
      </sec>
      <sec id="sec-4-5">
        <title>4.5. Access to structure fields</title>
        <p>Structures in Julia are immutable by default, that is, once we create the variable z, we can access
the fields of the structure itself using the point operator, but we cannot modify the value of
these fields:
@show z.x z. y # We can read the field value
z.x = 1 # Error! We can't change it.</p>
        <p>To create mutable data types, the mutable struct structure is provided. However, for
our example an immutable structure is more appropriate, since the requirement for mutability
imposes performance restrictions, which is undesirable for a numeric type.</p>
        <p>Julia doesn’t have access modifiers for fields like public or private, so structure fields are
always readable. However, it is considered a good programming style to encapsulate structure
ifelds and provide an interface for accessing them. This style is used, for example, for the built-in
type of complex numbers.</p>
        <p>There are two ways to encapsulate fields. The first method is to create special interface
functions for accessing fields. In our case it is suficient to define the following functions:
Base.real(z::Dual) = z.x
Base.imag(z::Dual) = z.y
Base.reim(z::Dual) = (z.x, z.y)</p>
        <p>The same functions are defined in the built-in Complex module. Next, we should call only
these functions everywhere to get the structure fields, instead of accessing the fields by name
directly. This will allow developers to refactor the structure in the future, for example, rename
or add new fields. Backward compatibility is easy to maintain by rewriting only the interface
functions. Performance will not sufer, since the JIT compiler will replace calls to these functions
directly with their code, because they are extremely simple (inline functions).</p>
        <p>The second approach is to use the getproperty function, which has been available since
version 0.7 of the Julia language. Overloading this function allows us to set additional names
for accessing fields in the structure. So, if we write the following method for example:
function Base.getproperty(z::Dual, p::Symbol)
if p in (:real, :a, :r) # real part aliases</p>
        <p>return getfield(z, :x)
elseif p in (:imag, :b, :i) # imaginary part aliases</p>
        <p>return getfield(z, :y)
else</p>
        <p>return getfield(z, p)
end
end
then we will be able to access the real and imaginary parts of the number  in four diferent
ways:
z.x == z.a == z.r == z.real # returns true
z.y == z.b == z.i == z.imag # returns true</p>
        <p>This approach is more flexible, since when changing the structure for backward compatibility,
it is enough to modify only one method getproperty, but not all the interface functions (if
the structure is complex it can by lots of such functions). In addition, the programmer can use
any name from the aliases list for accessing the structure fields.</p>
        <p>For the Dual type, we applied both approaches, since the presence of the functions real —
equivalent to Re( ) and imag — equivalent to Im( ) is mathematically justified.</p>
      </sec>
      <sec id="sec-4-6">
        <title>4.6. Unary functions</title>
        <p>The Base module defines the functions one and zero, which return a multiplication identity
element and an addition identity element, respectively. In the case of dual numbers, the
multiplication identity is the real unit, and the addition identity is the real zero.</p>
        <p>For the Dual type, we define the following single-line methods:
Base.one(::Type{Dual{T}}) where T &lt;: Real = Dual(one(T))
Base.one(z::Dual) = Dual(one(z.x))
Base.zero(::Type{Dual{T}}) where T &lt;: Real = Dual(zero(T))
Base.zero(z::Dual) = Dual(zero(z.x))</p>
        <p>The first version of the one and zero functions takes the data type as an argument and
returns one or zero of this type, respectively. Since for dual numbers it is 1 and 0 from ℝ, it is
suficient to return a dual number with a zero imaginary part. The real part will be 1 and 0 of
the parametric type T. For all standard types T&lt;:Real the one and zero methods are defined
in the Base module, which we used.</p>
        <p>The second variant of functions takes a specific object of the Dual type as an argument and
also returns a identity using methods from the Base module.</p>
        <p>For complex numbers in Base, the conjugation functions conj, the absolute value abs, and
the argument arg are defined:</p>
        <p>Base.conj(z::Dual) = Dual(z.x, -z.y)
Base.abs(z::Dual) = abs(z.x)
Base.abs2(z::Dual) = z.x^2
function Base.arg(z::Dual)
@assert !iszero(z.x)
return z.y / z.x
end</p>
        <p>For the arg method we have provided a check for the inequality of the real part of the dual
number to zero, since otherwise we will get a division by zero. Using the standard function
iszero allows us not to worry about accounting for errors in the representation of real numbers
using floating-point numbers. The @assert macro throws an exception AssertionError if
the actual part is equal to zero.</p>
        <p>The inv function defines the inverse number for this number z:
function Base.inv(z::Dual)
@assert !iszero(z.x)
return Dual(1/z.x, -z.y/z.x^2)
end
There should also be an exception for the null real part.</p>
      </sec>
      <sec id="sec-4-7">
        <title>4.7. Comparison function</title>
        <p>The Base module also defines a number of functions that return the truth, if a particular
condition is true. Some of these functons are:
• isreal is real number;
• isinteger is integer;
• isfinite the number is finite;
• isnan the argument has the type NaN;
• isinf the argument has the type Inf;
• iszero the number is an addition identity (zero);
• isone the number is a multiplication identity (one).</p>
        <p>Methods for these functions for the Dual one-to-one case repeat methods for the built-in
Complex type, so here we do not provide their source code.</p>
        <p>In addition to unary functions for dual numbers, it makes sense to implement methods for
the comparison operator ==. Operators in Julia are no diferent from functions, and adding
methods for them is exactly the same. The only diference is that we need to add the character
: after Base., and since the == operator consists of two characters, we must frame it with
parentheses:
Base.:(==)(z::Dual, u::Dual) = (real(z) == real(u)) &amp;&amp; (imag(z) ==
↪ imag(u))</p>
        <p>It makes sense to compare dual numbers with real numbers as well, if the dual number has a
zero imaginary part. In order for the operator to commute for arguments of diferent types, we
must define two methods:
Base.:(==)(z::Dual, x::Real) = isreal(z) &amp;&amp; real(z) == x
Base.:(==)(x::Real, z::Dual) = isreal(z) &amp;&amp; real(z) == x</p>
      </sec>
      <sec id="sec-4-8">
        <title>4.8. Arithmetic operations</title>
        <p>We also need to overload arithmetic operators such as the unary operators + and - and the
binary operators +, -, * and /. The implementation of the +, -, and * operators is trivial:
Base.:+(z::Dual) = Dual(+z.x, +z.y)
Base.:-(z::Dual) = Dual(-z.x, -z.y)
Base.:+(z::Dual, u::Dual) = Dual(z.x + u.x, z.y + u.y)
Base.:-(z::Dual, u::Dual) = Dual(z.x - u.x, z.y - u.y)
Base.:*(z::Dual, u::Dual) = Dual(z.x * u.x, z.x * u.y + z.y * u.x)
In the case of the / operator, we should take into account the equality of the divisor to zero:
function Base.:/(z::Dual, u::Dual)
@assert !iszero(u.x)
return Dual(z.x/u.x, (z.y*u.x-u.y*z.x)/u.x^2)
end</p>
        <p>For binary operators, we do not need to implement separate cases of arguments of diferent
types, because in this case the promotion mechanism to a common type will call promote
function automatically.</p>
      </sec>
      <sec id="sec-4-9">
        <title>4.9. Types promotion</title>
        <p>To make the type promotion mechanism to work, we must define the type promotion rules.
Without these rules, for example, the next operation fails with an error:
&gt;&gt;Dual(1, 2) + 2
ERROR: promotion of types Dual{Int64} and Int64 failed to change any
↪ arguments</p>
        <p>This error occurs because there is no rule that can be used to determine the common type for
numbers of the type Dual{Int64} and the type Int64.</p>
        <p>To define such a rule, we have to add a method for the promote_rule function from the
Base module. Any number of the Real type can participate in the arithmetic operation with a
dual number, since this number can be interpreted as a dual with a zero imaginary part:
Base.promote_rule(::Type{Dual{T}}, ::Type{S}) where {T&lt;:Real, S&lt;:Real}
↪ = Dual{promote_type(T, S)}</p>
        <p>We should also provide the rule that will work for operators with two dual numbers with
diferent parametric types T and S. In this case, we need to find a common type for the T and S
types:
Base.promote_rule(::Type{Dual{T}}, ::Type{Dual{S}}) where {T&lt;:Real,
↪ S&lt;:Real} = Dual{promote_type(T, S)}</p>
      </sec>
      <sec id="sec-4-10">
        <title>4.10. Raising to a rational degree</title>
        <p>To raise a number to a power, the ^ operator is used, which should also be overloaded for integer
and rational powers. In the case of an integer degree, we use the formula</p>
        <p>( + ε ) =   +   −1ε.</p>
        <p>There are several special cases to consider:
• if  = 0, then   = 1;
• if  = 1, then   =  ;
• if  &gt; 0, the formula works for any  and  ;
• if  &lt; 0, the condition  ≠ 0must be met.</p>
        <p>If we consider all these cases, we get the following implementation:
function Base.:^(z::Dual, n::Integer)::Dual
x, y = reim(z)
if n == 0</p>
        <p>return one(z)
elseif n == 1</p>
        <p>return z
elseif n &gt; 1
↪
else
end
end
end</p>
        <p>return Dual(x^n, n*y*x^(n-1))
else #n &lt; 0
if iszero(x)</p>
        <p>for real(z)!= 0"))
return Dual(x^n, n*y*x^(n-1))
throw(DomainError(:x, "negative exponentiation is only defined
exception DomainError (out of the range of acceptable values).</p>
        <p>If the real part of the dual number is zero (iszero(x)) and  &lt; 0, the function throws an
For a rational degree, the more complex formula is used:</p>
        <p>( + ε )  =   (1 + ε
 
  )</p>
        <p>=   + ε</p>
        <p>−1,


values includes  &lt; 0, and for an even  , you should limit yourself to  ⩾ 0:
for which the cases of even and odd  should be provided. For an odd  , the range of acceptable
function Base.:^(z::Dual, q::Rational)::Dual
x, y = reim(z)
n, d = numerator(q), denominator(q)
if n == 0</p>
        <p>return one(z)
elseif isodd(d)
else
if x &lt; 0
return Dual(x^q, q*y*x^(q-1))
throw(DomainError(:x, "even radical for dual number z is only
defined for real(z)&gt;=0"))
return Dual(x^q, q*y*x^(q-1))</p>
        <p>↪
else
end
end
end
cbrt:</p>
        <p>It makes sense to separately overload the functions for square and cubic roots sqrt and
function Base.sqrt(z::Dual)::Dual
x, y = reim(z)
if x &lt; 0
throw(DomainError(:x, "sqrt for dual number z is only defined
↪ for real(z)&gt;=0"))
else</p>
        <p>return Dual(√x, y/(2*√x))
end
end</p>
        <p>Base.cbrt(z::Dual) = Dual(cbrt(z.x), z.y*cbrt(z.x)/(3z.x))</p>
      </sec>
      <sec id="sec-4-11">
        <title>4.11. Elementary functions</title>
        <p>Elementary functions are calculated using the formula  ( + ε ) =  ( ) +  ′( ) ε. Note that
many of them are not defined for the case of the zero real part of the number  =  + ε :
function Base.exp(z::Dual)
e = exp(z.x)
return Dual(e, e * z.y)
end
Base.log(z::Dual) = Dual(log(z.x), z.y/z.x)
Base.log(b, z::Dual) = Dual(log(b, z.x), (z.y/z.x) * log(z.x))
#======== Trigonometric ========#
Base.sin(z::Dual) = Dual(sin(z.x), +z.y*cos(z.x))
Base.cos(z::Dual) = Dual(cos(z.x), -z.y*sin(z.x))
Base.tan(z::Dual) = Dual(tan(z.x), z.y/cos(z.x)^2)
Base.cot(z::Dual) = Dual(cot(z.x), -z.y/sin(z.x)^2)
Base.asin(z::Dual) = Dual(asin(z.x), z.y/sqrt(1-z.x^2))
Base.acos(z::Dual) = Dual(acos(z.x), -z.y/sqrt(1-z.x^2))
Base.atan(z::Dual) = Dual(atan(z.x), z.y/(1+z.x^2))
Base.acot(z::Dual) = Dual(acot(z.x), -z.y/(1+z.x^2))
#======== Hyperbolic ========#
Base.sinh(z::Dual) = Dual(sinh(z.x), z.y*cosh(z.x))
Base.cosh(z::Dual) = Dual(cosh(z.x), z.y*sinh(z.x))
Base.tanh(z::Dual) = Dual(tanh(z.x), z.y/cosh(z.x)^2)
Base.coth(z::Dual) = Dual(coth(z.x), z.y/sinh(z.x)^2)</p>
      </sec>
    </sec>
    <sec id="sec-5">
      <title>5. Results</title>
    </sec>
    <sec id="sec-6">
      <title>6. Discussion</title>
      <p>The paper describes the preliminary implementation of dual complex numbers and basic
operations on them in the Julia language.</p>
      <p>The implementation of dual complex numbers on Julia is completely based on the mechanism
of multiple dispatching. Thus, we not only implemented a certain set of operations on dual
numbers, but also demonstrated the power of this mechanism.</p>
      <p>
        It should also be noted that in contrast to the implementation of the Dual type in the
automatic diferentiation package ForwardDiff [
        <xref ref-type="bibr" rid="ref7">7</xref>
        ], our proposed implementation is cleaner.
For example, in the above package an ordinary complex number can be used as a coeficient
before a dual complex unit. It is clear that this is due to the specifics of using dual numbers in
this package for automatic diferentiation. But this type of number is more likely to belong to
quaternions [
        <xref ref-type="bibr" rid="ref15 ref15 ref16">15, 15, 16</xref>
        ], rather than the proper complex numbers.
      </p>
    </sec>
    <sec id="sec-7">
      <title>7. Conclusion</title>
      <p>In this paper, the prototype of the implementation of generalized complex numbers in the
Julia language was made, namely, the implementation of dual complex numbers. Having a
multiple dispatching mechanism in the Julia language makes it very easy to implement new
numeric types within the existing programming language infrastructure. We propose to further
extend this prototype for more general implementation of generalized complex numbers and
generalized quaternions.</p>
    </sec>
    <sec id="sec-8">
      <title>Acknowledgments</title>
      <p>The publication has been prepared with the support of the Russian Foundation for Basic Research
(RFBR) according to the research project No 19-01-00645.</p>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>I. M.</given-names>
            <surname>Yaglom</surname>
          </string-name>
          ,
          <string-name>
            <given-names>B. A.</given-names>
            <surname>Rozenfel'd</surname>
          </string-name>
          , E. U. Yasinskaya, Projective Metrics,
          <source>Russian Mathematical Surveys</source>
          <volume>19</volume>
          (
          <year>1964</year>
          )
          <fpage>49</fpage>
          -
          <lpage>107</lpage>
          . doi:
          <volume>10</volume>
          .1070/RM1964v019n05ABEH001159.
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <given-names>I. M.</given-names>
            <surname>Yaglom</surname>
          </string-name>
          , Complex Numbers in Geometry, Academic Press,
          <year>1968</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>B. A.</given-names>
            <surname>Rozenfel'd</surname>
          </string-name>
          ,
          <string-name>
            <given-names>I. M.</given-names>
            <surname>Yaglom</surname>
          </string-name>
          ,
          <article-title>On the geometries of the simplest algebras</article-title>
          ,
          <source>Mat. Sbornik N. S</source>
          .
          <volume>28</volume>
          (
          <issue>70</issue>
          ) (
          <year>1951</year>
          )
          <fpage>205</fpage>
          -
          <lpage>216</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          [4]
          <string-name>
            <given-names>D. S.</given-names>
            <surname>Kulyabov</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A. V.</given-names>
            <surname>Korolkova</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M. N.</given-names>
            <surname>Gevorkyan</surname>
          </string-name>
          ,
          <article-title>Hyperbolic numbers as Einstein numbers</article-title>
          ,
          <source>Journal of Physics: Conference Series</source>
          <volume>1557</volume>
          (
          <year>2020</year>
          )
          <volume>012027</volume>
          .
          <fpage>1</fpage>
          -
          <lpage>5</lpage>
          . doi:
          <volume>10</volume>
          .1088/
          <fpage>1742</fpage>
          -6596/1557/1/012027.
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          [5]
          <string-name>
            <given-names>A.</given-names>
            <surname>Cayley</surname>
          </string-name>
          ,
          <string-name>
            <surname>IV.</surname>
          </string-name>
          <article-title>A sixth memoir upon quantics</article-title>
          ,
          <source>Philosophical Transactions of the Royal Society of London</source>
          <volume>149</volume>
          (
          <year>1859</year>
          )
          <fpage>61</fpage>
          -
          <lpage>90</lpage>
          . doi:
          <volume>10</volume>
          .1098/rstl.
          <year>1859</year>
          .
          <volume>0004</volume>
          .
        </mixed-citation>
      </ref>
      <ref id="ref6">
        <mixed-citation>
          [6]
          <string-name>
            <given-names>F.</given-names>
            <surname>Klein</surname>
          </string-name>
          ,
          <article-title>Ueber die sogenannte Nicht-Euklidische Geometrie, in: Gauß und die Anfänge der nicht-euklidischen Geometrie, volume 4 of Teubner-Archiv zur Mathematik, SpringerVerlag Wien</article-title>
          , Wien,
          <year>1985</year>
          , pp.
          <fpage>224</fpage>
          -
          <lpage>238</lpage>
          . doi:
          <volume>10</volume>
          .1007/978-3-
          <fpage>7091</fpage>
          -9511-
          <issue>6</issue>
          _
          <fpage>5</fpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref7">
        <mixed-citation>
          [7]
          <string-name>
            <given-names>Forward</given-names>
            <surname>Mode Automatic Diferentiation for Julia</surname>
          </string-name>
          ,
          <year>2020</year>
          . URL: https://github.com/JuliaDif/ DualNumbers.jl.
        </mixed-citation>
      </ref>
      <ref id="ref8">
        <mixed-citation>
          [8]
          <string-name>
            <given-names>The</given-names>
            <surname>Julia Language</surname>
          </string-name>
          ,
          <year>2020</year>
          . URL: https://julialang.org/.
        </mixed-citation>
      </ref>
      <ref id="ref9">
        <mixed-citation>
          [9]
          <string-name>
            <given-names>J.</given-names>
            <surname>Bezanson</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Chen</surname>
          </string-name>
          ,
          <string-name>
            <given-names>B.</given-names>
            <surname>Chung</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Karpinski</surname>
          </string-name>
          ,
          <string-name>
            <given-names>V. B.</given-names>
            <surname>Shah</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Vitek</surname>
          </string-name>
          , L. Zoubritzky,
          <article-title>Julia: dynamism and performance reconciled by design</article-title>
          ,
          <source>Proceedings of the ACM on Programming Languages</source>
          <volume>2</volume>
          (
          <year>2018</year>
          )
          <fpage>1</fpage>
          -
          <lpage>23</lpage>
          . doi:
          <volume>10</volume>
          .1145/3276490.
        </mixed-citation>
      </ref>
      <ref id="ref10">
        <mixed-citation>
          [10]
          <string-name>
            <given-names>F.</given-names>
            <surname>Zappa Nardelli</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Belyakova</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Pelenitsyn</surname>
          </string-name>
          ,
          <string-name>
            <given-names>B.</given-names>
            <surname>Chung</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Bezanson</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Vitek</surname>
          </string-name>
          ,
          <article-title>Julia subtyping: a rational reconstruction</article-title>
          ,
          <source>Proceedings of the ACM on Programming Languages</source>
          <volume>2</volume>
          (
          <year>2018</year>
          )
          <fpage>1</fpage>
          -
          <lpage>27</lpage>
          . doi:
          <volume>10</volume>
          .1145/3276483.
        </mixed-citation>
      </ref>
      <ref id="ref11">
        <mixed-citation>
          [11]
          <string-name>
            <given-names>J.</given-names>
            <surname>Bezanson</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Edelman</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Karpinski</surname>
          </string-name>
          ,
          <string-name>
            <given-names>V. B.</given-names>
            <surname>Shah</surname>
          </string-name>
          ,
          <article-title>Julia: A fresh approach to numerical computing</article-title>
          ,
          <source>SIAM Review 59</source>
          (
          <year>2017</year>
          )
          <fpage>65</fpage>
          -
          <lpage>98</lpage>
          . doi:
          <volume>10</volume>
          .1137/141000671. arXiv:
          <volume>1411</volume>
          .
          <fpage>1607</fpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref12">
        <mixed-citation>
          [12]
          <string-name>
            <given-names>J.</given-names>
            <surname>Bezanson</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Karpinski</surname>
          </string-name>
          ,
          <string-name>
            <given-names>V. B.</given-names>
            <surname>Shah</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Edelman</surname>
          </string-name>
          , Julia:
          <string-name>
            <given-names>A Fast</given-names>
            <surname>Dynamic Language for Technical Computing</surname>
          </string-name>
          (
          <year>2012</year>
          )
          <fpage>1</fpage>
          -
          <lpage>27</lpage>
          . arXiv:
          <volume>1209</volume>
          .
          <fpage>5145</fpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref13">
        <mixed-citation>
          [13]
          <article-title>Julia package for representing dual numbers and for performing dual algebra</article-title>
          ,
          <year>2020</year>
          . URL: https://github.com/JuliaDif/ForwardDif.jl.
        </mixed-citation>
      </ref>
      <ref id="ref14">
        <mixed-citation>
          [14]
          <article-title>Oficial Julia language GitHub repository</article-title>
          ,
          <year>2020</year>
          . URL: https://github.com/JuliaLang/julia.
        </mixed-citation>
      </ref>
      <ref id="ref15">
        <mixed-citation>
          [15]
          <string-name>
            <given-names>W. R.</given-names>
            <surname>Hamilton</surname>
          </string-name>
          , Elements of Quaternions, Cambridge University Press, Cambridge,
          <year>1866</year>
          . doi:
          <volume>10</volume>
          .1017/CBO9780511707162.
        </mixed-citation>
      </ref>
      <ref id="ref16">
        <mixed-citation>
          [16]
          <string-name>
            <given-names>A. P.</given-names>
            <surname>Yefremov</surname>
          </string-name>
          , Quaternions and Biquaternions: Algebra, Geometry and
          <string-name>
            <given-names>Physical</given-names>
            <surname>Theories</surname>
          </string-name>
          ,
          <year>2005</year>
          . arXiv:
          <fpage>0501055</fpage>
          .
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>