Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Julia  Module 2 – Variables, numbers and arithmetic

Rationals, complex numbers and maths functions

Exact fractions with //, complex numbers for roots and AC circuits, log1p, expm1, hypot and sinpi for accurate results, and how π converts, on Julia 1.13.

  • Beginner
  • 10 minutes
  • Examples run with julia 1.13.1 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Compute exactly with Rational numbers, and say when Rational{Int64} overflows and what to use instead
  • Use complex numbers to find roots and to compute the impedance of a circuit, with its magnitude and phase
  • Pick accurate special-purpose functions such as log1p, expm1, hypot, sinpi and atan(y, x)
  • Explain how irrational constants such as π and ℯ convert in expressions

Before you start

What you’ll learn · Before you start

What you will learn

  • Compute exactly with Rational numbers, and say when Rational{Int64} overflows and what to use instead
  • Use complex numbers to find roots and to compute the impedance of a circuit, with its magnitude and phase
  • Pick accurate special-purpose functions such as log1p, expm1, hypot, sinpi and atan(y, x)
  • Explain how irrational constants such as π and ℯ convert in expressions

Before you start

On this page

Simpson’s rule gives the exact integral of a cubic, yet run with 10 steps in Float64, it returns 0.25000000000000006 for the integral of x³ from 0 to 1, whose value is 1/4. Run with Julia’s exact fractions, the same function returns 1//4, and the rounding is gone.

Below, every result is a run recorded on Julia 1.13.1: exact arithmetic with Rational numbers and where it stops, complex numbers for the roots of a quadratic and the impedance of a circuit, functions such as log1p and hypot that keep the digits a direct formula loses, and how the constants π and ℯ turn into numbers. This lesson ends the module, with a map of where you stand in it.

Rationals are exact until their integers overflow

// builds an exact fraction, a rational number: An exact fraction of two integers, written with // in Julia: 1//3 + 1//6 == 1//2. Julia keeps it in lowest terms and checks its arithmetic for overflow. of two integers. Here are a few, a float turned into one, Simpson’s rule in rationals and in floats, and a sum that gets too long for Int64:

rationals.jl: fractions, conversions and promotion Julia · rationals.jl
# // builds an exact fraction, kept in lowest terms with the sign on the numerator.
println("1//3 + 1//6 = ", 1//3 + 1//6, ", equal to 1//2: ", 1//3 + 1//6 == 1//2)
println("6//8 = ", 6//8, " (numerator ", numerator(6//8), ", denominator ", denominator(6//8), "), 5//-15 = ", 5//-15)
println("typeof(1//3) = ", typeof(1//3), ", float(1//3) = ", float(1//3))

# Converting floats: rationalize finds a short fraction, Rational gives the exact stored value.
println("rationalize(0.75) = ", rationalize(0.75), ", rationalize(0.1) = ", rationalize(0.1))
println("Rational(0.1) = ", Rational(0.1))
println("0.5 == 1//2: ", 0.5 == 1//2, ", 0.1 == 1//10: ", 0.1 == 1//10)
println("3//5 + 1 = ", 3//5 + 1, ", 3//5 - 0.5 = ", 3//5 - 0.5)
exact_steps.jl: Simpson's rule in two number types, and a sum that overflows Julia · exact_steps.jl
"Composite Simpson's rule with n intervals (n even), in whatever number type a and b have."
function simpson(f, a, b, n)
    h = (b - a) / n
    s = f(a) + f(b)
    for k in 1:n-1
        s += (isodd(k) ? 4 : 2) * f(a + k * h)
    end
    return s * h / 3
end

# Simpson's rule is exact for cubics: with rationals the result shows it.
cube(x) = x^3
println("∫₀¹ x³ dx, Rational: ", simpson(cube, 0//1, 1//1, 10))
println("∫₀¹ x³ dx, Float64:  ", simpson(cube, 0.0, 1.0, 10))
println("sum of the weights:  ", simpson(x -> 1, 0//1, 1//1, 10), " and ", simpson(x -> 1, 0.0, 1.0, 10))

# Rational{Int64} checks its arithmetic, so a long chain stops with an error instead of wrapping.
function harmonic(T, n)
    s = zero(Rational{T})
    for k in 1:n
        s += 1 // T(k)
    end
    return s
end
println("H(46) = ", harmonic(Int, 46))
try
    harmonic(Int, 47)
catch err
    println("H(47) → ", sprint(showerror, err))
end
println("H(47) with BigInt = ", harmonic(BigInt, 47))
integer_tools.jl: gcd, lcm, binomial and modular arithmetic Julia · integer_tools.jl
println("gcd(12, 18) = ", gcd(12, 18), ", lcm(4, 6) = ", lcm(4, 6))
println("binomial(50, 25) = ", binomial(50, 25))
println("binomial(big(70), 35) = ", binomial(big(70), 35))
println("invmod(3, 7) = ", invmod(3, 7), ", powermod(2, 100, 7) = ", powermod(2, 100, 7))
binomial(70, 35)   # too large for an Int64
rationals.jl, exact_steps.jl and integer_tools.jl, run with Julia 1.13.1 Python · rationals_session.py
from session import files, run

# Exact fractions, Simpson's rule in rationals and in floats, a rational sum that overflows, and integer tools.
files("rationals.jl", "exact_steps.jl", "integer_tools.jl")
run("""
julia rationals.jl
julia exact_steps.jl
!julia integer_tools.jl
""")

Output

$ julia rationals.jl
1//3 + 1//6 = 1//2, equal to 1//2: true
6//8 = 3//4 (numerator 3, denominator 4), 5//-15 = -1//3
typeof(1//3) = Rational{Int64}, float(1//3) = 0.3333333333333333
rationalize(0.75) = 3//4, rationalize(0.1) = 1//10
Rational(0.1) = 3602879701896397//36028797018963968
0.5 == 1//2: true, 0.1 == 1//10: false
3//5 + 1 = 8//5, 3//5 - 0.5 = 0.09999999999999998
$ julia exact_steps.jl
∫₀¹ x³ dx, Rational: 1//4
∫₀¹ x³ dx, Float64:  0.25000000000000006
sum of the weights:  1//1 and 1.0
H(46) = 5943339269060627227//1345655451257488800
H(47) → OverflowError: 5943339269060627227 * 47 overflowed for type Int64
H(47) with BigInt = 280682601097106968469//63245806209101973600
$ julia integer_tools.jl
gcd(12, 18) = 6, lcm(4, 6) = 12
binomial(50, 25) = 126410606437752
binomial(big(70), 35) = 112186277816662845432
invmod(3, 7) = 5, powermod(2, 100, 7) = 2
ERROR: LoadError: OverflowError: binomial(70, 35) overflows
Stacktrace:
 [1] binomial(n::Int64, k::Int64)
   @ Base ./intfuncs.jl:1339
 [2] top-level scope
   @ ~/demo/integer_tools.jl:5
 [3] include(mod::Module, _path::String)
   @ Base ./Base.jl:309
 [4] exec_options(opts::Base.JLOptions)
   @ Base ./client.jl:344
 [5] _start()
   @ Base ./client.jl:577
in expression starting at ~/demo/integer_tools.jl:5
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rationals_session.py

1//3 + 1//6 gave exactly 1//2, and 6//8 became 3//4: the manual says rationals are reduced to lowest terms, with the sign on the numerator, so 5//-15 is -1//3. Converting a float goes two ways. rationalize(0.1) finds the short fraction 1//10 that rounds to 0.1, while Rational(0.1) gives the exact value stored in the float, a fraction whose denominator is 2⁵⁵; that is why 0.1 == 1//10 is false while 0.5 == 1//2 is true. A rational with an integer stays rational, 3//5 + 1 is 8//5, and a rational with a float becomes a float.

Simpson’s rule ran unchanged on both types, because its code only adds, multiplies and divides: with 0//1 and 1//1 as limits every step was exact, and the integral of x³ came out as 1//4. Step through one addition:

Interactive · stage by stage How Julia adds 1//3 and 1//6, and where it refuses Step down the column for one addition of two fractions. The branch on the right is what happens when a product does not fit.
How Julia adds 1//3 and 1//6, and where it refuses, step 6 of 6. Showing 1//3 + 1//6, Divide by the gcd, Numerator 1×2 + 1×1 = 3, Denominator 3×2 = 6, Lowest terms. In focus: refuse.too large: OverflowError1//3 + 1//6two Rational{Int64} valuesDivide by the gcdgcd 3 leaves 1 and 2Numerator 1×2 + 1×1 = 3checked, so never wrappedDenominator 3×2 = 63//6 so farLowest termsdivide by gcd(3, 6): 1//2

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Two fractions. 1//3 + 1//6 adds two Rational{Int64} values, each kept as a numerator and a denominator in lowest terms.
  2. The common part. Julia first divides both denominators by their gcd, 3, which leaves 1 and 2. Working with the smaller numbers keeps the products small.
  3. The new numerator. The numerator is 1 × 2 + 1 × 1 = 3. Every product and sum here goes through checked_mul and checked_add, which throw instead of wrapping.
  4. The new denominator. The denominator is 3 × 2 = 6, also a checked product, so the sum so far is 3//6.
  5. Lowest terms. The constructor divides 3 and 6 by their gcd, 3, and returns 1//2, which is why 1//3 + 1//6 == 1//2 printed true.
  6. When a product does not fit. In the harmonic sum, adding 1//47 needed 5943339269060627227 × 47, which is beyond typemax(Int64). checked_mul threw an OverflowError, so the sum stopped instead of wrapping to a wrong fraction.

The documentation states it in one line: rationals are checked for overflow. The harmonic sum 1 + 1/2 + … + 1/46 still fitted, with a denominator of 1,345,655,451,257,488,800, but the 47th term needed a product beyond typemax(Int64), and the run stopped with an OverflowError instead of wrapping. Rational{BigInt} has no such limit.

The same integer toolbox shows up around fractions: gcd and lcm, binomial(50, 25) = 126,410,606,437,752, and modular arithmetic, where invmod(3, 7) is 5 because 3 × 5 = 15 leaves 1 when divided by 7. binomial(70, 35) does not fit an Int64 and throws, so its exact value came from binomial(big(70), 35).

One slash or two

Wrong: 1/3 when you mean the fraction. One slash divides in floating point and gives 0.3333333333333333. Right: 1//3, which stays exact, and float(1//3) at the end, when a decimal is needed.

Fraction Calculator Check a fraction result from your own code, with the reduction to lowest terms shown step by step.

Checkpoint

Check: Exact rationals and their limits

  1. Question 1 of 2 What does 6//8 + 1//8 give?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 7//8

    6//8 is stored as 3//4, and 3//4 + 1//8 = 6//8 + 1//8 = 7//8, already in lowest terms. The result stays an exact Rational{Int64}.

  2. Question 2 of 2 harmonic(Int, 47) stopped with an error. How does Rational{Int64} arithmetic differ from plain Int64 arithmetic here?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Its products and sums are checked, so it throws an OverflowError instead of wrapping

    Rational{Int64} uses checked multiplication and addition, so a numerator or denominator beyond typemax(Int64) raises an error. Use Rational{BigInt} for long exact chains.

Complex numbers carry a magnitude and a phase

Julia writes the imaginary unit as im, a global constant bound to i, the principal square root of −1; the manual explains that i and j were passed over because loops use them so often as names. A complex number: A number a + b·im with a real part a and an imaginary part b, where im² = −1. Julia's ComplexF64 has two Float64 parts; abs and angle give its size and direction. takes the type of its parts, so 2.0 + 3im is a ComplexF64. This run works through the basics, the roots of x² + 2x + 5 = 0, and then a circuit:

complex_basics.jl: parts, roots, Euler's formula and sqrt of a negative Julia · complex_basics.jl
println(typeof(2.0 + 3im), ", ", typeof(2 + 3im), ", im^2 = ", im^2)
z = 3 + 4im
println("abs = ", abs(z), ", angle = ", angle(z), ", conj = ", conj(z), ", real = ", real(z), ", imag = ", imag(z))

# From variables, build a complex number with complex(a, b).
a, b = 1.0, 2.0
println("complex(a, b) = ", complex(a, b))

# The roots of x^2 + 2x + 5 = 0: the discriminant is negative, so take a complex square root.
d = 2^2 - 4 * 1 * 5
println("roots: ", (-2 + sqrt(complex(d))) / 2, " and ", (-2 - sqrt(complex(d))) / 2)
println("exp(im * π) = ", exp(im * π), ", cis(π / 2) = ", cis(π / 2))
println(sqrt(-1))   # a real argument gets a real square root, or an error
rc_phasor.jl: the impedance of an RC circuit from 10 Hz to 100 kHz Julia · rc_phasor.jl
using Printf
# A series RC circuit: a 1 kΩ resistor and a 100 nF capacitor.
R = 1_000.0                          # ohms
C = 100e-9                           # farads
impedance(f) = R + 1 / (im * 2π * f * C)
fc = 1 / (2π * R * C)                # where the two parts of Z are equal in size

@printf("cut-off frequency fc = %.1f Hz\n", fc)
@printf("%9s %12s %10s\n", "f (Hz)", "|Z| (ohm)", "phase (°)")
for f in (10.0, 100.0, 1_000.0, fc, 10_000.0, 100_000.0)
    Z = impedance(f)
    @printf("%9.1f %12.1f %10.2f\n", f, abs(Z), rad2deg(angle(Z)))
end
Z = impedance(fc)
println("Z at fc = ", Z, ", |Z| / R = ", abs(Z) / R)
complex_basics.jl and rc_phasor.jl, run with Julia 1.13.1 Python · complex_session.py
from session import files, run

# Complex arithmetic, the roots of a quadratic, Euler's formula, sqrt of a negative real, then an RC circuit's impedance.
files("complex_basics.jl", "rc_phasor.jl")
run("""
!julia complex_basics.jl
julia rc_phasor.jl
""")

Output

$ julia complex_basics.jl
ComplexF64, Complex{Int64}, im^2 = -1 + 0im
abs = 5.0, angle = 0.9272952180016122, conj = 3 - 4im, real = 3, imag = 4
complex(a, b) = 1.0 + 2.0im
roots: -1.0 + 2.0im and -1.0 - 2.0im
exp(im * π) = -1.0 + 1.2246467991473532e-16im, cis(π / 2) = 6.123233995736766e-17 + 1.0im
ERROR: LoadError: DomainError with -1.0:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
 [1] throw_complex_domainerror(f::Symbol, x::Float64)
   @ Base.Math ./math.jl:33
 [2] sqrt
   @ ./math.jl:645 [inlined]
 [3] sqrt(x::Int64)
   @ Base.Math ./math.jl:1343
 [4] top-level scope
   @ ~/demo/complex_basics.jl:13
 [5] include(mod::Module, _path::String)
   @ Base ./Base.jl:309
 [6] exec_options(opts::Base.JLOptions)
   @ Base ./client.jl:344
 [7] _start()
   @ Base ./client.jl:577
in expression starting at ~/demo/complex_basics.jl:13
[exit status 1]
$ julia rc_phasor.jl
cut-off frequency fc = 1591.5 Hz
   f (Hz)    |Z| (ohm)  phase (°)
     10.0     159158.1     -89.64
    100.0      15946.9     -86.40
   1000.0       1879.6     -57.86
   1591.5       1414.2     -45.00
  10000.0       1012.6      -9.04
 100000.0       1000.1      -0.91
Z at fc = 1000.0 - 999.9999999999999im, |Z| / R = 1.414213562373095

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 complex_session.py

abs(3 + 4im) is 5.0 and angle gives its direction in radians. complex(a, b) builds a complex number from two variables, the form the manual recommends over a + b*im. The quadratic’s discriminant is −16, and sqrt(complex(d)) gave the roots −1 ± 2im. sqrt(-1) itself stopped the first file with a DomainError whose message says what to do: call sqrt(Complex(x)). A real argument always gets a real result or an error, never a sometimes-complex one, so the type of sqrt(x) stays predictable. exp(im * π) printed −1.0 + 1.22e-16im, not exactly −1, because π became the nearest Float64.

The second program applies phasor: A complex number that stands for a sine wave of one frequency: its absolute value is the amplitude and its angle the phase. Impedances in AC circuits are used the same way. analysis to a circuit. A resistor’s impedance: The complex ratio of voltage to current in an AC circuit: R for a resistor and 1/(iωC) for a capacitor of C farads at the angular frequency ω. In series, impedances add. is R and a capacitor’s is 1/(iωC), so impedance(f) is one line, and abs and angle read off the magnitude and the phase. At 10 Hz the capacitor dominates, 159,158.1 Ω at −89.64°; at 100 kHz the resistor does, 1000.1 Ω at −0.91°; and at the cut-off frequency, 1/(2πRC) = 1591.5 Hz, the two parts are equal. Step through that row:

Interactive · stage by stage The impedance of the RC circuit at its cut-off frequency Step down the column from a frequency to a magnitude and a phase, the way impedance(f) computes them.
The impedance of the RC circuit at its cut-off frequency, step 5 of 5. Showing f = 1591.5 Hz, ω = 2πf, Capacitor: 1/(im·ω·C), Z = R + capacitor, abs(Z) and angle(Z). In focus: abs(Z) and angle(Z).f = 1591.5 Hzthe cut-off, 1/(2πRC)ω = 2πf10,000 rad/sCapacitor: 1/(im·ω·C)−1000im ohmsZ = R + capacitor1000 − 1000im ohmsabs(Z) and angle(Z)1414.2 ohms at −45°

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The frequency. Start at the cut-off frequency, fc = 1/(2πRC) = 1591.5 Hz for a 1 kΩ resistor and a 100 nF capacitor.
  2. Angular frequency. ω = 2πf is 10,000 radians a second. At the cut-off it is exactly 1/(RC).
  3. The capacitor. A capacitor's impedance is 1/(iωC). With im for i, 1/(im × 10,000 × 100e-9) is −1000im ohms: purely imaginary and negative.
  4. In series. In series the impedances add: Z = 1000 − 1000im. The run printed 1000.0 − 999.9999999999999im, the last digit lost to rounding in 2π.
  5. Magnitude and phase. abs(Z) is √(1000² + 1000²) = 1414.2 ohms, √2 times R, and rad2deg(angle(Z)) is −45°, as the row for 1591.5 Hz printed.

At the cut-off the magnitude is √2 times R and the phase −45°, and 1591.5 Hz is the cut-off frequency that a filter calculator reports for the same two parts. Rounding showed up even here: Z printed as 1000.0 − 999.9999999999999im, and |Z|/R as 1.414213562373095, one unit in the last place from √2.

RC, LC & Active Filter Calculator Enter R = 1 kΩ and C = 100 nF for a low-pass RC filter and compare its cut-off frequency with the program's 1591.5 Hz.

Checkpoint

Check: Complex numbers and phasors

  1. Question 1 of 2 What does sqrt(complex(-4)) return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0.0 + 2.0im

    sqrt follows the type of its argument: a real argument gets a real result or a DomainError, and a complex one gets the complex root. Wrap a negative number in complex() when a complex answer is what you want.

  2. Question 2 of 2 At the cut-off frequency of the lesson's RC circuit, what was the phase of its impedance?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: −45°

    At fc = 1/(2πRC) the capacitor's −1000im ohms equals R in size, so |Z| is √2 × R = 1414.2 ohms and the phase is −45°, as the row for 1591.5 Hz printed.

Special functions keep the digits a direct formula loses

Some formulas lose digits however carefully they are typed, because of where the rounding falls: log(1 + x) rounds 1 + x before the logarithm sees it. Base has a function for each common case. This run sets the direct formula against the special function:

small_x.jl: six formulas against six special functions Julia · small_x.jl
x = 1e-12
println("log(1 + x) = ", log(1 + x), ", log1p(x) = ", log1p(x))
println("exp(x) - 1 = ", exp(x) - 1, ", expm1(x) = ", expm1(x))
println("sqrt(a^2 + b^2) = ", sqrt(1e200^2 + 1e200^2), ", hypot(a, b) = ", hypot(1e200, 1e200))
println("sin(2π) = ", sin(2π), ", sinpi(2) = ", sinpi(2), ", cos(π/2) = ", cos(π / 2), ", cospi(0.5) = ", cospi(0.5))
println("atan(1 / -1) = ", rad2deg(atan(1 / -1)), "°, atan(1, -1) = ", rad2deg(atan(1, -1)), "°")
try
    println("(-27.0)^(1/3) = ", (-27.0)^(1 / 3))
catch err
    println("(-27.0)^(1/3) → ", sprint(showerror, err))
end
println("cbrt(-27) = ", cbrt(-27))
small_x.jl, run with Julia 1.13.1 Python · smallx_session.py
from session import files, run

# Six functions that keep the digits, or the range, that the direct formula loses.
files("small_x.jl")
run("""
julia small_x.jl
""")

Output

$ julia small_x.jl
log(1 + x) = 1.000088900581841e-12, log1p(x) = 9.999999999995e-13
exp(x) - 1 = 1.000088900582341e-12, expm1(x) = 1.0000000000005e-12
sqrt(a^2 + b^2) = Inf, hypot(a, b) = 1.414213562373095e200
sin(2π) = -2.4492935982947064e-16, sinpi(2) = 0.0, cos(π/2) = 6.123233995736766e-17, cospi(0.5) = 0.0
atan(1 / -1) = -45.0°, atan(1, -1) = 135.0°
(-27.0)^(1/3) → DomainError with -27.0:
Exponentiation yielding a complex result requires a complex argument.
Replace x^y with (x+0im)^y, Complex(x)^y, or similar.
cbrt(-27) = -3.0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 smallx_session.py

At x = 1e-12, log(1 + x) printed 1.000088900581841e-12, wrong from the fifth digit, because 1 + 1e-12 had already been rounded to the float grid near 1.0; log1p(x) printed 9.999999999995e-13, correct to the last digit. expm1 does the same for exp(x) − 1. The table says which function replaces which formula, and why:

Instead of Write Because
log(1 + x) log1p(x) 1 + x rounds away the digits of a small x
exp(x) - 1 expm1(x) the subtraction cancels when exp(x) is near 1
sqrt(a^2 + b^2) hypot(a, b) a² overflows to Inf for a = 1e200
sin(π * x) sinpi(x) π * x is rounded before sin sees it
atan(y / x) atan(y, x) y / x forgets which quadrant the point is in
x^(1/3) cbrt(x) a negative x has no real power 1/3, but a real cube root

hypot(1e200, 1e200) returned 1.414213562373095e200 where the direct formula overflowed to Inf. sinpi(2) and cospi(0.5) gave exactly 0.0, while sin(2π) gave −2.4e-16 and cos(π/2) 6.1e-17, because 2π and π/2 had been rounded first. atan(1, -1) gave 135°, the true direction of the point (−1, 1), while atan(1 / -1) gave −45°, the direction of the opposite point. And the cube root written as a power, (-27.0)^(1/3), stopped with a DomainError where cbrt(-27) gave −3.0.

A cube root as a power

Wrong: (-27.0)^(1/3), which throws a DomainError, because a negative number raised to a non-integer power has no real value. Right: cbrt(-27.0), which returns the real cube root, −3.0.

Checkpoint

Check: Accurate special functions

  1. Question 1 of 2 Which call computes log(1 + x) accurately for x = 1e-12?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: log1p(x)

    log1p(x) computes log(1 + x) without first rounding 1 + x. Its partner expm1(x) does the same for exp(x) − 1.

  2. Question 2 of 2 The point (−1, 1) lies up and to the left. Which call gives its direction as 135°?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: rad2deg(atan(1, -1))

    atan of a ratio cannot tell (−1, 1) from (1, −1), because both ratios are −1. atan(y, x) takes the two coordinates separately and returns a direction from −180° to 180°.

Irrational constants take the precision of the expression they meet

π is not a Float64. It is a value of type Irrational{:π}, an Irrational: Julia's type for exact mathematical constants such as π and ℯ. Such a value has no fixed precision: it is rounded to Float32, Float64 or BigFloat when it meets one in an expression. that stands for the exact constant, and the documentation says such a value is rounded to the correct precision in arithmetic with other numbers. Here is π in several types, and the tests where its exactness shows:

irrationals.jl: π and ℯ in expressions Julia · irrationals.jl
println(π, " :: ", typeof(π))
println("Float64(π) = ", Float64(π), ", Float32(π) = ", Float32(π))
println("big(π) = ", big(π))
p128 = setprecision(BigFloat, 128) do
    big(π)
end
println("big(π) at 128 bits = ", p128)
println("precision: ", precision(big(π)), " bits by default, ", precision(p128), " inside setprecision")
println("typeof(2π) = ", typeof(2π), ", typeof(π * 1.0f0) = ", typeof(π * 1.0f0), ", typeof(π * big(1.0)) = ", typeof(π * big(1.0)))
println("π == 3.141592653589793: ", π == 3.141592653589793, ", Float64(π) == 3.141592653589793: ", Float64(π) == 3.141592653589793)
println("sin(π) = ", sin(π), ", sin(Float64(π)) = ", sin(Float64(π)))
println("ℯ = ", ℯ, ", Float64(ℯ) = ", Float64(ℯ), ", exp(1) == Float64(ℯ): ", exp(1) == Float64(ℯ))
irrationals.jl, run with Julia 1.13.1 Python · irrationals_session.py
from session import files, run

# π and ℯ as exact constants, and the type each takes in an expression.
files("irrationals.jl")
run("""
julia irrationals.jl
""")

Output

$ julia irrationals.jl
π :: Irrational{:π}
Float64(π) = 3.141592653589793, Float32(π) = 3.1415927
big(π) = 3.141592653589793238462643383279502884197169399375105820974944592307816406286198
big(π) at 128 bits = 3.141592653589793238462643383279502884195
precision: 256 bits by default, 128 inside setprecision
typeof(2π) = Float64, typeof(π * 1.0f0) = Float32, typeof(π * big(1.0)) = BigFloat
π == 3.141592653589793: false, Float64(π) == 3.141592653589793: true
sin(π) = 0.0, sin(Float64(π)) = 1.2246467991473532e-16
ℯ = ℯ, Float64(ℯ) = 2.718281828459045, exp(1) == Float64(ℯ): true

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 irrationals_session.py

2π is a Float64, π * 1.0f0 a Float32, and π * big(1.0) a BigFloat with 256 bits, about 77 digits; inside setprecision(BigFloat, 128) do … end the same big(π) had 128 bits. Writing Float32(π) or big(π) directly gives the best value of that type, so mixing π into single-precision code costs no precision and forces no conversion to Float64.

Because π is exact, π == 3.141592653589793 is false: Julia’s source defines an irrational as never equal to a finite number, while Float64(π) == 3.141592653589793 is true. And sin(π) printed exactly 0.0 because Base defines sin for π itself, while sin(Float64(π)) gave 1.22e-16. That special case covers π alone; sin(2π) was not 0.0, so for multiples of π use sinpi. ℯ, typed as \euler and Tab, works the same way, and exp(1) == Float64(ℯ) printed true.

Key idea

π and ℯ are exact constants that become a number only when they meet one, at that number’s precision, so write sinpi(x) for sin(πx) and never compare an irrational with ==.

Checkpoint

Check: How π and ℯ convert

  1. Question 1 of 2 What is typeof(π * 1.0f0)?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Float32

    An Irrational is rounded to the type of the other operand: Float32 with a Float32, BigFloat with a BigFloat, and Float64 with an integer or a Float64.

  2. Question 2 of 2 What does π == 3.141592653589793 return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: false

    π == x is false for every float or rational x, because an irrational has no finite representation. Convert first, Float64(π) == x, when you mean the Float64 nearest π.

Practise: exact, complex and accurate numbers

The answers to these questions are in the recorded runs. Practice starts with the idea you are least sure of and explains every choice you make:

Practice that adapts to you

Practise this lesson

12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 What is typeof(1//3)?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: Rational{Int64}

    1//3 is the exact fraction one third, a Rational{Int64}. float(1//3) turns it into the Float64 0.3333333333333333.

  2. Question 2 of 12 What is the difference between rationalize(0.1) and Rational(0.1)?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: rationalize finds the short fraction 1//10, while Rational gives the exact stored value, 3602879701896397//36028797018963968

    0.1 is stored as 3602879701896397/2^55. Rational(0.1) gives that exactly; rationalize(0.1) gives 1//10, the fraction you probably meant, because it rounds to the same float.

  3. Question 3 of 12 Which job suits Rational{Int64} best?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: The weights of a 10-interval Simpson rule, computed once and checked to add up to exactly 1

    Rationals shine for short exact computations with small integers. Long chains need Rational{BigInt}, and values that are not fractions, such as measurements and irrational coefficients, belong in floats.

  4. Question 4 of 12 What is typeof(2 + 3im)?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: Complex{Int64}

    A complex number takes the type of its parts: 2 + 3im is a Complex{Int64}, and 2.0 + 3im a ComplexF64.

  5. Question 5 of 12 Why does sqrt(-1) throw a DomainError instead of returning im?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: sqrt of a real argument returns a real result, so the type of the result never depends on the value

    Keeping sqrt's result type fixed by its argument type lets code that uses it be compiled for one type. Ask for a complex result with sqrt(complex(x)).

  6. Question 6 of 12 At 10 Hz the RC circuit's |Z| was 159,158.1 Ω at −89.64°. Why so large, and so close to −90°?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: At low frequency 1/(ωC) is huge and purely imaginary, so the capacitor dominates the 1 kΩ resistor

    A capacitor's impedance is 1/(iωC), which grows without limit as the frequency falls. At 10 Hz it is about 159 kΩ, so Z points almost straight down the negative imaginary axis.

  7. Question 7 of 12 What does expm1(x) compute?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: exp(x) − 1, accurately even when x is tiny

    At x = 1e-12, exp(x) − 1 printed 1.000088900582341e-12, wrong from the fifth digit, while expm1(x) printed the correct 1.0000000000005e-12.

  8. Question 8 of 12 Why did sqrt(1e200^2 + 1e200^2) give Inf while hypot(1e200, 1e200) gave 1.414213562373095e200?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: 1e200^2 is 1e400, beyond the largest Float64 of about 1.8e308, so it overflowed before the square root

    The documentation says hypot avoids overflow and underflow. A direct formula that squares large or tiny numbers can fail even when its answer fits easily.

  9. Question 9 of 12 Which expression gives exactly 0.0?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: sinpi(2)

    sinpi and cospi take the multiple of π as their argument, so whole and half multiples come out exact. Only sin of the constant π itself is special-cased.

  10. Question 10 of 12 What is typeof(π)?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: Irrational{:π}

    π is an Irrational, a symbol for the exact value, converted to a number of the right type when used.

  11. Question 11 of 12 How many bits of precision does big(π) have by default?

    Type a number.

    Show the answer: Question 11 of 12

    Answer: 256 bits

    BigFloat's default precision is 256 bits, about 77 decimal digits, as precision(big(π)) printed; inside setprecision(BigFloat, 128) do … end the same call gave 128 bits.

  12. Question 12 of 12 sin(π) printed exactly 0.0, but sin(2π) did not. Why?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Base defines sin for the constant π itself, while 2π is a rounded Float64

    A few functions have special methods for π, such as sin, cos and tan. For any other multiple of π use sinpi and cospi, which are exact at whole and half multiples.

Interview questions

Warm-up (fresher to 2–5 years): for which jobs is a Rational the better choice than a Float64 in Julia, and what does it cost?

Use a Rational when the answer must be exact and every value is a fraction of integers: probabilities of discrete events, weights of a quadrature rule, coefficients of a polynomial, ratios in a recipe or a gear train. 1//3 + 1//6 is exactly 1//2, and Simpson’s rule run on rationals gave 1//4 where floats gave 0.25000000000000006. The costs are speed, because every operation finds a gcd to keep the fraction in lowest terms; size, because numerators and denominators can grow quickly in long computations; and range, because Rational{Int64} checks its arithmetic and throws an OverflowError once they no longer fit, as the harmonic sum did at its 47th term. Switch to Rational{BigInt} for long exact chains, and convert with float at the end.

Two harder questions follow, about the impedance of a resonant circuit and about probabilities so small that the direct formulas fail. Note your answer first, then open each first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to senior

    Report the magnitude and phase of a series RLC circuit's impedance from 10 Hz to 100 kHz, and its resonant frequency, for R = 100 Ω, L = 10 mH and C = 100 nF. How do you write it in Julia, and how do you check the numbers?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A reliability model computes log(1 - p) for failure probabilities p near 1e-12, and 1 - (1 - p)^n for the chance of at least one failure in n = 1,000 trials. Both results are wrong from the fifth digit. Why, and how do you rewrite them?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • // builds exact rationals in lowest terms; rationalize finds a short fraction for a float and Rational its exact stored value, and Rational{Int64} throws an OverflowError rather than wrapping, so long chains need Rational{BigInt}.
  • im is the imaginary unit and complex numbers take the type of their parts; abs and angle give a phasor’s magnitude and phase, and sqrt(-1) is an error while sqrt(complex(-1)) is im.
  • log1p, expm1, hypot, sinpi, atan(y, x) and cbrt keep the digits, range or quadrant that the direct formulas lose.
  • π and ℯ are Irrational values that take the precision of the expression they meet, are never == to a float, and get exact results only where Base defines them, such as sin(π).

Terms in this lesson

complex number
A number a + b·im with a real part a and an imaginary part b, where im² = −1. Julia's ComplexF64 has two Float64 parts; abs and angle give its size and direction.
impedance
The complex ratio of voltage to current in an AC circuit: R for a resistor and 1/(iωC) for a capacitor of C farads at the angular frequency ω. In series, impedances add.
Irrational
Julia's type for exact mathematical constants such as π and ℯ. Such a value has no fixed precision: it is rounded to Float32, Float64 or BigFloat when it meets one in an expression.
phasor
A complex number that stands for a sine wave of one frequency: its absolute value is the amplitude and its angle the phase. Impedances in AC circuits are used the same way.
rational number
An exact fraction of two integers, written with // in Julia: 1//3 + 1//6 == 1//2. Julia keeps it in lowest terms and checks its arithmetic for overflow.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.