mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-20 15:27:45 +03:00
Add Num, Int, Frac sections
This commit is contained in:
parent
6fd9736886
commit
97cbba2eb6
159
TUTORIAL.md
159
TUTORIAL.md
@ -1114,113 +1114,70 @@ As such, it's very important to design your integer operations not to exceed the
|
||||
Roc has three fractional types:
|
||||
|
||||
* `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
* `F64`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
* `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
* `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
|
||||
|
||||
All of these are different from integers in that they can represent numbers with fractional components,
|
||||
These are different from integers in that they can represent numbers with fractional components,
|
||||
such as 1.5 and -0.123.
|
||||
|
||||
## [Dec] is the best default choice for representing base-10 decimal numbers
|
||||
## like currency, because it is base-10 under the hood. In contrast,
|
||||
## [F64] and [F32] are base-2 under the hood, which can lead to decimal
|
||||
## precision loss even when doing addition and subtraction. For example, when
|
||||
## using [F64], running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125,
|
||||
## whereas when using [Dec], 0.1 + 0.2 returns 0.3.
|
||||
##
|
||||
## Under the hood, a [Dec] is an [I128], and operations on it perform
|
||||
## [base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
|
||||
## with 18 decimal places of precision.
|
||||
##
|
||||
## This means a [Dec] can represent whole numbers up to slightly over 170
|
||||
## quintillion, along with 18 decimal places. (To be precise, it can store
|
||||
## numbers betwween `-170_141_183_460_469_231_731.687303715884105728`
|
||||
## and `170_141_183_460_469_231_731.687303715884105727`.) Why 18
|
||||
## decimal places? It's the highest number of decimal places where you can still
|
||||
## convert any [U64] to a [Dec] without losing information.
|
||||
##
|
||||
## There are some use cases where [F64] and [F32] can be better choices than [Dec]
|
||||
## despite their precision issues. For example, in graphical applications they
|
||||
## can be a better choice for representing coordinates because they take up
|
||||
## less memory, certain relevant calculations run faster (see performance
|
||||
## details, below), and decimal precision loss isn't as big a concern when
|
||||
## dealing with screen coordinates as it is when dealing with currency.
|
||||
##
|
||||
## ## Performance
|
||||
##
|
||||
## [Dec] typically takes slightly less time than [F64] to perform addition and
|
||||
## subtraction, but 10-20 times longer to perform multiplication and division.
|
||||
## [sqrt] and trigonometry are massively slower with [Dec] than with [F64].
|
||||
Dec : Float [ @Decimal128 ]
|
||||
`Dec` is the best default choice for representing base-10 decimal numbers
|
||||
like currency, because it is base-10 under the hood. In contrast,
|
||||
`F64` and `F32` are base-2 under the hood, which can lead to decimal
|
||||
precision loss even when doing addition and subtraction. For example, when
|
||||
using `F64`, running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125,
|
||||
whereas when using `Dec`, 0.1 + 0.2 returns 0.3.
|
||||
|
||||
## A fixed-size number with a fractional component.
|
||||
##
|
||||
## Roc fractions come in two flavors: fixed-point base-10 and floating-point base-2.
|
||||
##
|
||||
## * [Dec] is a 128-bit [fixed-point](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) base-10 number. It's a great default choice, especially when precision is important - for example when representing currency. With [Dec], 0.1 + 0.2 returns 0.3.
|
||||
## * [F64] and [F32] are [floating-point](https://en.wikipedia.org/wiki/Floating-point_arithmetic) base-2 numbers. They sacrifice precision for lower memory usage and improved performance on some operations. This makes them a good fit for representing graphical coordinates. With [F64], 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125.
|
||||
##
|
||||
## If you don't specify a type, Roc will default to using [Dec] because it's
|
||||
## the least error-prone overall. For example, suppose you write this:
|
||||
##
|
||||
## wasItPrecise = 0.1 + 0.2 == 0.3
|
||||
##
|
||||
## The value of `wasItPrecise` here will be `True`, because Roc uses [Dec]
|
||||
## by default when there are no types specified.
|
||||
##
|
||||
## In contrast, suppose we use `f32` or `f64` for one of these numbers:
|
||||
##
|
||||
## wasItPrecise = 0.1f64 + 0.2 == 0.3
|
||||
##
|
||||
## Here, `wasItPrecise` will be `False` because the entire calculation will have
|
||||
## been done in a base-2 floating point calculation, which causes noticeable
|
||||
## precision loss in this case.
|
||||
##
|
||||
## The floating-point numbers ([F32] and [F64]) also have three values which
|
||||
## are not ordinary [finite numbers](https://en.wikipedia.org/wiki/Finite_number).
|
||||
## They are:
|
||||
## * ∞ ([infinity](https://en.wikipedia.org/wiki/Infinity))
|
||||
## * -∞ (negative infinity)
|
||||
## * *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN))
|
||||
##
|
||||
## These values are different from ordinary numbers in that they only occur
|
||||
## when a floating-point calculation encounters an error. For example:
|
||||
## * Dividing a positive [F64] by `0.0` returns ∞.
|
||||
## * Dividing a negative [F64] by `0.0` returns -∞.
|
||||
## * Dividing a [F64] of `0.0` by `0.0` returns [*NaN*](Num.isNaN).
|
||||
##
|
||||
## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
## floating point standard. Because almost all modern processors are built to
|
||||
## this standard, deviating from these rules has a significant performance
|
||||
## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
|
||||
## access to hardware-accelerated performance, Roc follows these rules exactly.
|
||||
##
|
||||
## There's no literal syntax for these error values, but you can check to see if
|
||||
## you ended up with one of them by using [isNaN], [isFinite], and [isInfinite].
|
||||
## Whenever a function in this module could return one of these values, that
|
||||
## possibility is noted in the function's documentation.
|
||||
##
|
||||
## ## Performance Notes
|
||||
##
|
||||
## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32]
|
||||
## for addition and subtraction. For example, [F32] and [F64] do addition using
|
||||
## a single CPU floating-point addition instruction, which typically takes a
|
||||
## few clock cycles to complete. In contrast, [Dec] does addition using a few
|
||||
## CPU integer arithmetic instructions, each of which typically takes only one
|
||||
## clock cycle to complete. Exact numbers will vary by CPU, but they should be
|
||||
## similar overall.
|
||||
##
|
||||
## [Dec] is significantly slower for multiplication and division. It not only
|
||||
## needs to do more arithmetic instructions than [F32] and [F64] do, but also
|
||||
## those instructions typically take more clock cycles to complete.
|
||||
##
|
||||
## With [Num.sqrt] and trigonometry functions like [Num.cos], there is
|
||||
## an even bigger performance difference. [F32] and [F64] can do these in a
|
||||
## single instruction, whereas [Dec] needs entire custom procedures - which use
|
||||
## loops and conditionals. If you need to do performance-critical trigonometry
|
||||
## or square roots, either [F64] or [F32] is probably a better choice than the
|
||||
## usual default choice of [Dec], despite the precision problems they bring.
|
||||
Float a : Num [ @Fraction a ]
|
||||
`F32` and `F64` have direct hardware support on common processors today. There is no hardware support
|
||||
for fixed-point decimals, so under the hood, a `Dec` is an `I128`; operations on it perform
|
||||
[base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
|
||||
with 18 decimal places of precision.
|
||||
|
||||
This means a `Dec` can represent whole numbers up to slightly over 170
|
||||
quintillion, along with 18 decimal places. (To be precise, it can store
|
||||
numbers betwween `-170_141_183_460_469_231_731.687303715884105728`
|
||||
and `170_141_183_460_469_231_731.687303715884105727`.) Why 18
|
||||
decimal places? It's the highest number of decimal places where you can still
|
||||
convert any `U64] to a `Dec` without losing information.
|
||||
|
||||
While the fixed-point `Dec` has a fixed range, the floating-point `F32` and `F64` do not.
|
||||
Instead, outside of a certain range they start to lose precision instead of immediately overflowing
|
||||
the way integers and `Dec` do. `F64` can represent [between 15 and 17 significant digits](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) before losing precision, whereas `F32` can only represent [between 6 and 9](https://en.wikipedia.org/wiki/Single-precision_floating-point_format#IEEE_754_single-precision_binary_floating-point_format:_binary32).
|
||||
|
||||
There are some use cases where `F64` and `F32` can be better choices than `Dec`
|
||||
despite their precision drawbacks. For example, in graphical applications they
|
||||
can be a better choice for representing coordinates because they take up less memory,
|
||||
various relevant calculations run faster, and decimal precision loss isn't as big a concern
|
||||
when dealing with screen coordinates as it is when dealing with something like currency.
|
||||
|
||||
### Num, Int, and Frac
|
||||
|
||||
Some operations work on specific numeric types - such as `I64` or `Dec` - but operations support
|
||||
multiple numeric types. For example, the `Num.abs` function works on any number, since you can
|
||||
take the [absolute value](https://en.wikipedia.org/wiki/Absolute_value) of integers and fractions alike.
|
||||
Its type is:
|
||||
|
||||
```elm
|
||||
abs : Num a -> Num a
|
||||
```
|
||||
|
||||
This type says `abs` takes a number and then returns a number of the same type. That's because the
|
||||
`Num` type is compatible with both integers and fractions.
|
||||
|
||||
There's also an `Int` type which is only compatible with integers, and a `Frac` type which is only
|
||||
compatible with fractions. For example:
|
||||
|
||||
```elm
|
||||
Num.xor : Int a, Int a -> Int a
|
||||
```
|
||||
|
||||
```elm
|
||||
Num.cos : Frac a -> Frac a
|
||||
```
|
||||
|
||||
When you write a number literal in Roc, it has the type `Num *`. So you could call `Num.xor 1 1`
|
||||
and also `Num.cos 1` and have them all work as expected; the number literal `1` has the type
|
||||
`Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason,
|
||||
you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`.
|
||||
|
||||
## Interface modules
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user