Welcome to Day 12/25 of this yearโs **Perl 6 One-Liner Advent Calendar**! Today, we will examine a one-liner that computes a zero.

say 0.1 + 0.2 - 0.3

If you are familiar with programming, you know well that as soon as you start using floating-point arithmetic, you loose precision, and you can face the small errors very quickly.

You might also saw the website,ย 0.30000000000000004.com that has a long list of different programming languages and how they print a simple expressionย `0.1 + 0.2`. In most cases, you donโt get an exact value of 0.3. And often when you get it, it is actually the result of rounding during the print operation.

In Perl 6, 0.1 + 0.2 is exactly 0.3, and todayโs one-liner prints an exact zero.

Let us dive a bit to Perl 6 internals to see how that works. A few days ago, we saw that the Grammar of Perl 6 (implemented in the Rakudo compiler) has the following fragment that detects numbers:

token numish { [ | 'NaN' >> | <integer> | <dec_number> | <rad_number> | <rat_number> | <complex_number> | 'Inf' >> | $<uinf>='โ' | <unum=:No+:Nl> ] }

Most likely you are familiar enough with Perl 6, and you know that the above behaviour is explained by the fact that Perl 6 uses rational numbers to store floating-point numbers such as 0.1. Thatโs right, but looking at the grammar, you will see that the journey is a bit longer.

What is called `rat_number` in the grammar is a number written in angle brackets:

token rat_number { '<' <bare_rat_number> '>' }

token bare_rat_number {

<?before <.[-โ+0..9<>:boxd]>+? '/'>

<nu=.signed-integer> '/' <de=integer>

}

So if you change the program to:

say <1/10> + <2/10> - <3/10>

then you will immediately be operating rational numbers. Here is a corresponding action that converts numbers in this format:

method rat_number($/) { make $<bare_rat_number>.ast }

method bare_rat_number($/) {

my $nu := $<nu>.ast.compile_time_value;

my $de := $<de>.ast;

my $ast := $*W.add_constant(

'Rat', 'type_new', $nu, $de,:nocache(1));

$ast.node($/);

make $ast;

}

At some point, the abstract syntax tree gets a node containing a constant of the `Rat` type with the `$nu` and `$de` parts as numerator and denominator.

In our example, with numbers written in the form of 0.1, they first pass the `dec_number` token:

token dec_number {

:dba('decimal number')

[

| $<coeff> = [ '.' <frac=.decint> ] <escale>?

| $<coeff> = [<int=.decint> '.' <frac=.decint>]

<escale>?

| $<coeff> = [ <int=.decint> ] <escale>

]

}

The integer and the fractional parts of the number get into the `<int>` and `<frac>` keys of the final Match object. The action method for this grammar token is rather complex. Let me show it to you.

method dec_number($/) { if $<escale> { # wants a Num make $*W.add_numeric_constant: $/, 'Num', ~$/; } else { # wants a Rat my $Int := $*W.find_symbol(['Int']); my $parti; my $partf; # we build up the number in parts if nqp::chars($<int>) { $parti := $<int>.ast; } else { $parti := nqp::box_i(0, $Int); } if nqp::chars($<frac>) { $partf := nqp::radix_I( 10, $<frac>.Str, 0, 4, $Int); $parti := nqp::mul_I($parti, $partf[1], $Int); $parti := nqp::add_I($parti, $partf[0], $Int); $partf := $partf[1]; } else { $partf := nqp::box_i(1, $Int); } my $ast := $*W.add_constant('Rat', 'type_new', $parti, $partf,:nocache(1)); $ast.node($/); make $ast; } }

For each of the numbers 0.1, 0.2, and 0.3, the above code takes their integer and fractional parts, prepares the two integers, `$parti` and `$partf`, and passes them to the same constructor of a new constant as we saw in the `rat_number` action, and after that you get a `Rat` number.

Now we skip some details, and will take a look at another important part with rational numbers that you have to know about them.

In our example, the integer and the fractional part get the following values:

$parti=1, $partf=10

$parti=2, $partf=10

$parti=3, $partf=10

You can easily see it yourself if you hack on your local copy of Rakudo files.

Alternatively, use the `--target=parse` option in the command line:

$ perl6 --target=parse -e'say 0.1 + 0.2 - 0.3'

A part of the output will contain the data we want to see:

- 0: 0.1 - value: 0.1 - number: 0.1 - numish: 0.1 - dec_number: 0.1 - frac: 1 - int: 0 - coeff: 0.1 - 1: 0.2 - value: 0.2 - number: 0.2 - numish: 0.2 - dec_number: 0.2 - coeff: 0.2 - frac: 2 - int: 0

Having the numbers presented as fractions, it is quite easy to make exact calculations, and thatโs why we see a pure zero in the output.

Returning to our fractions. If you print numerator and denominator (using the `nude` method, for example), you will see that the fraction is normalised if possible:

> <1/10>.nude.say

(1 10)

> <2/10>.nude.say

(1 5)

> 0.2.nude.say

(1 5)

> 0.3.nude.say

(3 10)

As you see, instead of 2/10 we have 1/5, which represents the same number with the same accuracy. When you use the number, you should not worry about finding the common divider for both fractions, for example:

> (0.1 + 0.2).nude.say

(3 10)

I hope there was not too much nudity behind a trivially-looking one-liner today. See you tomorrow with another portion of something interesting with Perl 6!

## 3 thoughts on “๐ 12/25. Whatโs behind 0.1+0.2 in Perl 6”