#### Floating Point Types

In the previous chapter we have seen that despite their ease of use, arithmetic operations on integers are prone to programming errors due to overflow and truncation. We have also seen that integers cannot have values with fractional parts, as in 1.25.

Floating point types are designed to support fractional parts. The "point" in their name comes from the *radix point*, which separates the integer part from the fractional part, and "floating" refers to a detail in how these types are implemented: the decimal point *floats* left and right as appropriate. (This detail is not important when using these types.)

We must cover important details in this chapter as well. Before doing that, I would like to give a list of some of the interesting aspects of floating point types:

- Adding 0.001 a thousand times is not the same as adding 1.
- Using the logical operators
`==`

and`!=`

with floating point types is erroneous in most cases. - The initial value of floating point types is
`.nan`

, not 0.`.nan`

may not be used in expressions in any meaningful way. When used in comparison operations,`.nan`

is not less than nor greater than any value. - The two overflow values are
`.infinity`

and negative`.infinity`

.

Although floating point types are more useful in some cases, they have peculiarities that every programmer must know. Compared to integers, they are very good at avoiding truncation because their main purpose is to support fractional values. Like any other type, being based on a certain numbers of bits, they too are prone to overflow, but compared to integers, the range of values that they can support is vast. Additionally, instead of being silent in the case of overflow, they get the special values of positive and negative *infinity*.

As a reminder, the floating point types are the following:

Type | Number of Bits | Initial Value |
---|---|---|

float | 32 | float.nan |

double | 64 | double.nan |

real | at least 64, maybe more (e.g. 80, depending on hardware support) |
real.nan |

##### Floating point type properties

Floating point types have more properties than other types:

`.stringof`

is the name of the type.`.sizeof`

is the length of the type in terms of bytes. (In order to determine the bit count, this value must be multiplied by 8, the number of bits in a`byte`

.)-
`.max`

is the short for "maximum" and is the maximum value that the type can have. There is no separate`.min`

property for floating types; the negative of`.max`

is the minimum value that the type can have. For example, the minimum value of`double`

is`-double.max`

. -
`.min_normal`

is the smallest*normalized*value that this type can have. The type can have smaller values than`.min_normal`

but the precision of those values is less than the normal precision of the type and they are generally slower to compute. The condition of a floating point value being between`-.min_normal`

and`.min_normal`

(excluding 0) is called*underflow*. -
`.dig`

is short for "digits" and specifies the number of digits that signify the precision of the type. -
`.infinity`

is the special value used to denote overflow.

Other properties of floating point types are used less commonly. You can see all of them at Properties for Floating Point Types at dlang.org.

The properties of floating point types and their relations can be shown on a number line like the following:

+ +─────────+─────────+ ... + ... +─────────+─────────+ + │ -max -1 │ 0 │ 1 max │ │ │ │ │ -infinity -min_normal min_normal infinity

Other than the two special infinity values, the line above is to scale: the number of values that can be represented between `min_normal`

and 1 is equal to the number of values that can be represented between 1 and `max`

. This means that the precision of the fractional parts of the values that are between `min_normal`

and 1 is very high. (The same is true for the negative side as well.)

##### `.nan`

We have already seen that this is the default value of floating point variables. `.nan`

may appear as a result of meaningless floating point expressions as well. For example, the floating point expressions in the following program all produce `double.nan`

:

import std.stdio; void main() { double zero = 0; double infinity = double.infinity; writeln("any expression with nan: ", double.nan + 1); writeln("zero / zero : ", zero / zero); writeln("zero * infinity : ", zero * infinity); writeln("infinity / infinity : ", infinity / infinity); writeln("infinity - infinity : ", infinity - infinity); }

`.nan`

is not useful just because it indicates an uninitialized value. It is also useful because it is propagated through computations, making it easier and earlier to detect errors.

##### Specifying floating point values

Floating point values can be built from integer values without a decimal point, like 123, or created directly with a decimal point, like 123.0.

Floating point values can also be specified with the special floating point syntax, as in `1.23e+4`

. The `e+`

part in that syntax can be read as "times 10 to the power of". According to that reading, the previous value is "1.23 times 10 to the power of 4", which is the same as "1.23 times 10^{4}", which in turn is the same as 1.23x10000, being equal to 12300.

If the value after `e`

is negative, as in `5.67e-3`

, then it is read as "divided by 10 to the power of". Accordingly, this example is "5.67 divided by 10^{3}", which in turn is the same as 5.67/1000, being equal to 0.00567.

The floating point format is apparent in the output of the following program that prints the properties of the three floating point types:

import std.stdio; void main() { writeln("Type : ", float.stringof); writeln("Precision : ", float.dig); writeln("Minimum normalized value: ", float.min_normal); writeln("Minimum value : ", -float.max); writeln("Maximum value : ", float.max); writeln(); writeln("Type : ", double.stringof); writeln("Precision : ", double.dig); writeln("Minimum normalized value: ", double.min_normal); writeln("Minimum value : ", -double.max); writeln("Maximum value : ", double.max); writeln(); writeln("Type : ", real.stringof); writeln("Precision : ", real.dig); writeln("Minimum normalized value: ", real.min_normal); writeln("Minimum value : ", -real.max); writeln("Maximum value : ", real.max); }

The output of the program is the following in my environment. Since `real`

depends on the hardware, you may get a different output:

Type : float Precision : 6 Minimum normalized value: 1.17549e-38 Minimum value : -3.40282e+38 Maximum value : 3.40282e+38 Type : double Precision : 15 Minimum normalized value: 2.22507e-308 Minimum value : -1.79769e+308 Maximum value : 1.79769e+308 Type : real Precision : 18 Minimum normalized value: 3.3621e-4932 Minimum value : -1.18973e+4932 Maximum value : 1.18973e+4932

###### Observations

As you will remember from the previous chapter, the maximum value of `ulong`

has 20 digits: 18,446,744,073,709,551,616. That value looks small when compared to even the smallest floating point type: `float`

can have values up to the 10^{38} range, e.g. 340,282,000,000,000,000,000,000,000,000,000,000,000. The maximum value of `real`

is in the range 10^{4932}, a value with more than 4900 digits!

As another observation, let's look at the minimum value that `double`

can represent with 15-digit precision:

0.000...(there are 300 more zeroes here)...0000222507385850720

##### Overflow is not ignored

Despite being able to take very large values, floating point types are prone to overflow as well. The floating point types are safer than integer types in this regard because overflow is not ignored. The values that overflow on the positive side become `.infinity`

, and the values that overflow on the negative side become `‑.infinity`

. To see this, let's increase the value of `.max`

by 10%. Since the value is already at the maximum, increasing by 10% would overflow:

import std.stdio; void main() { real value = real.max; writeln("Before : ", value); // Multiplying by 1.1 is the same as adding 10% value *= 1.1; writeln("Added 10% : ", value); // Let's try to reduce its value by dividing in half value /= 2; writeln("Divided in half: ", value); }

Once the value overflows and becomes `real.infinity`

, it remains that way even after being divided in half:

Before : 1.18973e+4932 Added 10% : inf Divided in half: inf

##### Precision

Precision is a concept that we come across in daily life but do not talk about much. Precision is the number of digits that is used when specifying a value. For example, when we say that the third of 100 is 33, the precision is 2 because 33 has 2 digits. When the value is specified more precisely as 33.33, then the precision is 4 digits.

The number of bits that each floating type has not only affects its maximum value, but also its precision. The greater the number of bits, the more precise the values are.

##### There is no truncation in division

As we have seen in the previous chapter, integer division cannot preserve the fractional part of a result:

int first = 3; int second = 2; writeln(first / second);

Output:

1

Floating point types don't have this *truncation* problem; they are specifically designed for preserving the fractional parts:

double first = 3; double second = 2; writeln(first / second);

Output:

1.5

The accuracy of the fractional part depends on the precision of the type: `real`

has the highest precision and `float`

has the lowest precision.

##### Which type to use

Unless there is a specific reason otherwise, you can choose `double`

for floating point values. `float`

has low precision but due to being smaller than the other types it may be useful when memory is limited. On the other hand, since the precision of `real`

is higher than `double`

on some hardware, it would be preferable for high precision calculations.

##### Cannot represent all values

We cannot represent certain values in our daily lives. In the decimal system that we use daily, the digits before the decimal point represent ones, tens, hundreds, etc. and the digits after the decimal point represent tenths, hundredths, thousandths, etc.

If a value is created from a combination of these values, it can be represented exactly. For example, because the value 0.23 consists of 2 tenths and 3 hundredths it is represented exactly. On the other hand, the value 1/3 cannot be exactly represented in the decimal system because the number of digits is always insufficient, no matter how many are specified: 0.33333...

The situation is very similar with the floating point types. Because these types are based on a certain numbers of bits, they cannot represent every value exactly.

The difference with the binary system that the computers use is that the digits before the decimal point are ones, twos, fours, etc. and the digits after the decimal point are halves, quarters, eighths, etc. Only the values that are exact combinations of those digits can be represented exactly.

A value that cannot be represented exactly in the binary system used by computers is 0.1, as in 10 cents. Although this value can be represented exactly in the decimal system, its binary representation never ends and continuously repeats four digits: 0.0001100110011... (Note that the value is written in binary system, not decimal.) It is always inaccurate at some level depending on the precision of the floating point type that is used.

The following program demonstrates this problem. The value of a variable is being incremented by 0.001 a thousand times in a loop. Surprisingly, the result is not 1:

import std.stdio; void main() { float result = 0; // Adding 0.001 for a thousand times: int counter = 1; while (counter <= 1000) { result += 0.001; ++counter; } if (result == 1) { writeln("As expected: 1"); } else { writeln("DIFFERENT: ", result); } }

Because 0.001 cannot be represented exactly, that inaccuracy affects the result at every iteration:

DIFFERENT: 0.999991

**Note:** The variable `counter`

above is a loop counter. Defining a variable explicitly for that purpose is not recommended. Instead, a common approach is to use a `foreach`

loop, which we will see in a later chapter.

##### Comparing floating point values

We have seen the following comparison operations for integers: equal to (`==`

), not equal to (`!=`

), less than (`<`

), greater than (`>`

), less than or equal to (`<=`

), and greater than or equal to (`>=`

). Floating point types have many more comparison operators.

Since the special value `.nan`

represents invalid floating point values, some comparisons with other values are not meaningful. For example, it is meaningless to ask whether `.nan`

or 1 is greater.

For that reason, floating point values introduce another comparison concept: unordered. Being unordered means that at least one of the values is `.nan`

.

The following table lists all the floating point comparison operators. All of them are binary operators (meaning that they take two operands) and used as in `left == right`

. The columns that contain `false`

and `true`

are the results of the comparison operations.

The last column indicates whether the operation is meaningful if one of the operands is `.nan`

. For example, even though the result of the expression `1.2 < real.nan`

is `false`

, that result is meaningless because one of the operands is `real.nan`

. The result of the reverse comparison `real.nan < 1.2`

would produce `false`

as well. The abreviation lhs stands for *left-hand side*, indicating the expression on the left-hand side of each operator.

Operator |
Meaning |
If lhs is greater |
If lhs is less |
If both are equal |
If at least one is .nan |
Meaningful with .nan |
---|---|---|---|---|---|---|

== | is equal to | false | false | true | false | yes |

!= | is not equal to | true | true | false | true | yes |

> | is greater than | true | false | false | false | no |

>= | is greater than or equal to | true | false | true | false | no |

< | is less than | false | true | false | false | no |

<= | is less than or equal to | false | true | true | false | no |

!<>= | is not less than, not greater than, nor equal to | false | false | false | true | yes |

<> | is less than or greater than | true | true | false | false | no |

<>= | is less than, greater than, or equal to | true | true | true | false | no |

!<= | is not less than nor equal to | true | false | false | true | yes |

!< | is not less than | true | false | true | true | yes |

!>= | is not greater than nor equal to | false | true | false | true | yes |

!> | is not greater than | false | true | true | true | yes |

!<> | is not less than nor greater than | false | false | true | true | yes |

Although meaningful to use with `.nan`

, the `==`

operator always produces `false`

when used with a `.nan`

value. This is the case even when both values are `.nan`

:

import std.stdio; void main() { if (double.nan == double.nan) { writeln("equal"); } else { writeln("not equal"); } }

Although one would expect `double.nan`

to be equal to itself, the result of the comparison is `false`

:

not equal

###### `isNaN()`

for `.nan`

equality comparison

As we have seen above, it is not possible to use the `==`

operator to determine whether the value of a floating point variable is `.nan`

:

if (variable == double.nan) { // ← WRONG // ... }

`isNaN()`

function from the `std.math`

module is for determining whether a value is `.nan`

:

import std.math; // ... if (isNaN(variable)) { // ← correct // ... }

Similarly, to determine whether a value is *not* `.nan`

, one must use `!isNaN()`

because otherwise the `!=`

operator would always produce `true`

.

##### Exercises

- Instead of
`float`

, use`double`

(or`real`

) in the program above which added 0.001 a thousand times:`double result = 0;`

This exercise demonstrates how misleading floating point equality comparisons can be.

- Modify the calculator from the previous chapter to support floating point types. The new calculator should work more accurately with that change. When trying the calculator, you can enter floating point values in various formats, as in 1000, 1.23, and 1.23e4.
- Write a program that reads 5 floating point values from the input. Make the program first print twice of each value and then one fifth of each value.
This exercise is a preparation for the array concept of the next chapter. If you write this program with what you have seen so far, you will understand arrays more easily and will better appreciate them.