[Floating Point] Integrate the proposal into the spec (#3408)

This commit is contained in:
Natalie Weizenbaum 2022-10-03 16:01:22 -07:00 committed by GitHub
parent 1d442b9505
commit 78d28d300b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 401 additions and 202 deletions

View File

@ -14,6 +14,11 @@ though some have special handling of units.
* [Variables](#variables)
* [`$e`](#e)
* [`$pi`](#pi)
* [`$epsilon`](#epsilon)
* [`$max-safe-integer`](#max-safe-integer)
* [`$min-safe-integer`](#min-safe-integer)
* [`$max-number`](#max-number)
* [`$min-number`](#min-number)
* [Functions](#functions)
* [Bounding Functions](#bounding-functions)
* [`ceil()`](#ceil)
@ -34,7 +39,6 @@ though some have special handling of units.
* [`asin()`](#asin)
* [`atan()`](#atan)
* [`atan2()`](#atan2)
* [Edge cases](#edge-cases)
* [`cos()`](#cos)
* [`sin()`](#sin)
* [`tan()`](#tan)
@ -51,13 +55,57 @@ though some have special handling of units.
### `$e`
Equal to the value of the mathematical constant `e` with a precision of 10
digits after the decimal point: `2.7182818285`.
A unitless number whose value is the closest possible [double] approximation of
the [mathematical constant e].
[double]: ../types/number.md#double
[mathematical constant e]: https://en.wikipedia.org/wiki/E_(mathematical_constant)
> This is `2.718281828459045`.
### `$pi`
Equal to the value of the mathematical constant `pi` with a precision of 10
digits after the decimal point: `3.1415926536`.
A unitless number whose value is the closest possible [double] approximation of
the [mathematical constant π].
[mathematical constant π]: https://en.wikipedia.org/wiki/Pi
> This is `3.141592653589793`.
### `$epsilon`
A unitless number whose value is the difference between 1 and the smallest
[double] greater than 1.
> This is `2.220446049250313e-16`.
### `$max-safe-integer`
A unitless number whose value represents the maximum mathematical integer `n`
such that `n` and `n + 1` both have an exact [double] representation.
> This is `9007199254740991`.
### `$min-safe-integer`
A unitless number whose value represents the minimum mathematical integer `n`
such that `n` and `n - 1` both have an exact [double] representation.
> This is `-9007199254740991`.
### `$max-number`
A unitless number whose value represents the greatest finite number that can be
represented by a [double].
> This is `1.7976931348623157e+308`.
### `$min-number`
A unitless number whose value represents the least positive number that can be
represented by a [double].
> This is `5e-324`.
## Functions
@ -71,6 +119,12 @@ ceil($number)
This function is also available as a global function named `ceil()`.
* Return a number whose value is the result of
`convertToIntegerTowardPositive($number.value)` as defined by [IEEE 754 2019],
§5.8; and whose units are the same as `$number`'s.
[IEEE 754 2019]: https://ieeexplore.ieee.org/document/8766229
#### `clamp()`
```
@ -95,6 +149,10 @@ floor($number)
This function is also available as a global function named `floor()`.
* Return a number whose value is the result of
`convertToIntegerTowardNegative($number.value)` as defined by [IEEE 754 2019],
§5.8; and whose units are the same as `$number`'s.
#### `max()`
```
@ -119,6 +177,10 @@ round($number)
This function is also available as a global function named `round()`.
* Return a number whose value is the result of
`convertToIntegerTiesToAway($number.value)` as defined by [IEEE 754 2019],
§5.8; and whose units are the same as `$number`'s.
### Distance Functions
#### `abs()`
@ -129,6 +191,9 @@ abs($number)
This function is also available as a global function named `abs()`.
* Return a number whose value is the result of `abs($number.value)` as defined
by [IEEE 754 2019], §5.5.1; and whose units are the same as `$number`'s.
#### `hypot()`
```
@ -156,16 +221,15 @@ hypot($numbers...)
log($number, $base: null)
```
* If `$number` has units, throw an error.
* If `$base` is null:
* If `$number < 0`, return `NaN` as a unitless number.
* If `$number == 0`, return `-Infinity` as a unitless number.
* If `$number == Infinity`, return `Infinity` as a unitless number.
* Return the [natural log] of `$number`, as a unitless number.
* Otherwise, return the natural log of `$number` divided by the natural log of
`$base`, as a unitless number.
[natural log]: https://en.wikipedia.org/wiki/Natural_logarithm
* If `$number` has units, throw an error.
* Return a unitless number whose value is the result of `log($number.value)` as
defined by [IEEE 754 2019], §9.2.
> This is the [natural logarithm].
>
> [natural logarithm]: https://en.wikipedia.org/wiki/Natural_logarithm
#### `pow()`
@ -175,36 +239,8 @@ pow($base, $exponent)
* If `$base` or `$exponent` has units, throw an error.
* If `$exponent == 0`, return `1` as a unitless number.
* Otherwise, if `$exponent == Infinity` or `$exponent == -Infinity`:
* If `$base == 1` or `$base == -1`, return `NaN` as a unitless number.
* If `$base < -1` or `$base > 1` and if `$exponent > 0`, *or* if `$base > -1`
and `$base < 1` and `$exponent < 0`, return `Infinity` as a
unitless number.
* Return `0` as a unitless number.
* Otherwise:
* If `$base < 0` and `$exponent` is not an integer, return `NaN` as a unitless
number.
* If `$base == 0` and `$exponent < 0`, or if `$base == Infinity` and
`$exponent > 0`, return `Infinity` as a unitless number.
* If `$base == -0` and `$exponent < 0`, or if `$base == -Infinity` and
`$exponent > 0`:
* If `$exponent` is an odd integer, return `-Infinity` as a unitless number.
* Return `Infinity` as a unitless number.
* If `$base == 0` and `$exponent > 0`, or if `$base == Infinity` and
`$exponent < 0`, return `0` as a unitless number.
* If `$base == -0` and `$exponent > 0`, or if `$base == -Infinity` and
`$exponent < 0`:
* If `$exponent` is an odd integer, return `-0` as a unitless number.
* Return `0` as a unitless number.
* Return `$base` raised to the power of `$exponent`, as a unitless number.
* Return a unitless number whose value is the result of `pow($number.value,
$exponent.value)` as defined by [IEEE 754 2019], §9.2.
#### `sqrt()`
@ -213,11 +249,9 @@ sqrt($number)
```
* If `$number` has units, throw an error.
* If `$number < 0`, return `NaN` as a unitless number.
* If `$number == -0`, return `-0` as a unitless number.
* If `$number == 0`, return `0` as a unitless number.
* If `$number == Infinity`, return `Infinity` as a unitless number.
* Return the square root of `$number`, as a unitless number.
* Return a unitless number whose value is the result of `rootn($number.value,
2)` as defined by [IEEE 754 2019], §9.2.
### Trigonometric Functions
@ -237,11 +271,13 @@ acos($number)
```
* If `$number` has units, throw an error.
* If `$number < -1` or `$number > 1`, return `NaN` as a number in `deg`.
* If `$number == 1`, return `0deg`.
* Return the [arccosine] of `$number`, as a number in `deg`.
[arccosine]: https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Basic_properties
* Let `result` be a number in `rad` whose value is the result of
`acos($number.value)` as defined by [IEEE 754 2019], §9.2.
* Return the result of [converting `result` to `deg`].
[converting `result` to `deg`]: ../spec/types/number.md#converting-a-number-to-a-unit
#### `asin()`
@ -250,12 +286,11 @@ asin($number)
```
* If `$number` has units, throw an error.
* If `$number < -1` or `$number > 1`, return `NaN` as a number in `deg`.
* If `$number == -0`, return `-0deg`.
* If `$number == 0`, return `0deg`.
* Return the [arcsine] of `$number`, as a number in `deg`.
[arcsine]: https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Basic_properties
* Let `result` be a number in `rad` whose value is the result of
`asin($number.value)` as defined by [IEEE 754 2019], §9.2.
* Return the result of [converting `result` to `deg`].
#### `atan()`
@ -264,13 +299,11 @@ atan($number)
```
* If `$number` has units, throw an error.
* If `$number == -0`, return `-0deg`.
* If `$number == 0`, return `0deg`.
* If `$number == -Infinity`, return `-90deg`.
* If `$number == Infinity`, return `90deg`.
* Return the [arctangent] of `$number`, as a number in `deg`.
[arctangent]: https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Basic_properties
* Let `result` be a number in `rad` whose value is the result of
`atan($number.value)` as defined by [IEEE 754 2019], §9.2.
* Return the result of [converting `result` to `deg`].
#### `atan2()`
@ -284,89 +317,13 @@ atan2($y, $x)
```
* If the units of `$y` and `$x` are not [compatible], throw an error.
* If `$y` has units and `$x` does not, or vice-versa, throw an error.
* If the inputs match one of the following edge cases, return the provided
number. Otherwise, return the [2-argument arctangent] of `$y` and `$x`, as a
number in `deg`.
[2-argument arctangent]: https://en.wikipedia.org/wiki/Atan2
* Let `result` be a number in `rad` whose value is the result of
`atan2($y.value, $x.value)` as defined by [IEEE 754 2019], §9.2.
##### Edge cases
<table>
<thead>
<tr>
<td colspan="2"></td>
<th colspan="6" style="text-align: center">X</th>
</tr>
<tr>
<td colspan="2"></td>
<th>Infinity</th>
<th>-finite</th>
<th>-0</th>
<th>0</th>
<th>finite</th>
<th>Infinity</th>
</tr>
</thead>
<tbody>
<tr>
<th rowspan="6">Y</th>
<th>Infinity</th>
<td>-135deg</td>
<td>-90deg</td>
<td>-90deg</td>
<td>-90deg</td>
<td>-90deg</td>
<td>-45deg</td>
</tr>
<tr>
<th>-finite</th>
<td>-180deg</td>
<td></td>
<td>-90deg</td>
<td>-90deg</td>
<td></td>
<td>-0deg</td>
</tr>
<tr>
<th>-0</th>
<td>-180deg</td>
<td>-180deg</td>
<td>-180deg</td>
<td>-0deg</td>
<td>-0deg</td>
<td>-0deg</td>
</tr>
<tr>
<th>0</th>
<td>180deg</td>
<td>180deg</td>
<td>180deg</td>
<td>0deg</td>
<td>0deg</td>
<td>0deg</td>
</tr>
<tr>
<th>finite</th>
<td>180deg</td>
<td></td>
<td>90deg</td>
<td>90deg</td>
<td></td>
<td>0deg</td>
</tr>
<tr>
<th>Infinity</th>
<td>135deg</td>
<td>90deg</td>
<td>90deg</td>
<td>90deg</td>
<td>90deg</td>
<td>45deg</td>
</tr>
</tbody>
</table>
* Return the result of [converting `result` to `deg`].
#### `cos()`
@ -374,13 +331,13 @@ atan2($y, $x)
cos($number)
```
* If `$number` has units but is not an angle, throw an error.
* If `$number` is unitless, treat it as though its unit were `rad`.
* If `$number == Infinity` or `$number == -Infinity`, return `NaN` as a unitless
number.
* Return the [cosine] of `$number`, as a unitless number.
* Let `double` be the value of [converting `$number` to `rad`] allowing
unitless.
[cosine]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
[converting `$number` to `rad`]: #converting-a-number-to-units
* Return a unitless number whose value is the result of `cos(double)` as defined
by [IEEE 754 2019], §9.2.
#### `sin()`
@ -388,15 +345,13 @@ cos($number)
sin($number)
```
* If `$number` has units but is not an angle, throw an error.
* If `$number` is unitless, treat it as though its unit were `rad`.
* If `$number == Infinity` or `$number == -Infinity`, return `NaN` as a unitless
number.
* If `$number == -0`, return `-0` as a unitless number.
* If `$number == 0`, return `0` as a unitless number.
* Return the [sine] of `$number`, as a unitless number.
* Let `double` be the value of [converting `$number` to `rad`] allowing
unitless.
[sine]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
[converting `$number` to `rad`]: #converting-a-number-to-units
* Return a unitless number whose value is the result of `sin(double)` as defined
by [IEEE 754 2019], §9.2.
#### `tan()`
@ -404,19 +359,13 @@ sin($number)
tan($number)
```
* If `$number` has units but is not an angle, throw an error.
* If `$number` is unitless, treat it as though its unit were `rad`.
* If `$number == Infinity` or `$number == -Infinity`, return `NaN` as a unitless
number.
* If `$number == -0`, return `-0` as a unitless number.
* If `$number == 0`, return `0` as a unitless number.
* If `$number` is equivalent to `90deg +/- 360deg * n`, where `n` is any
integer, return `Infinity` as a unitless number.
* If `$number` is equivalent to `-90deg +/- 360deg * n`, where `n` is any
integer, return `-Infinity` as a unitless number.
* Return the [tangent] of `$number`, as a unitless number.
* Let `double` be the value of [converting `$number` to `rad`] allowing
unitless.
[tangent]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
[converting `$number` to `rad`]: #converting-a-number-to-units
* Return a unitless number whose value is the result of `tan(double)` as defined
by [IEEE 754 2019], §9.2.
### Unit Functions
@ -464,8 +413,8 @@ div($number1, $number2)
unquoted string whose contents is the result of serializing `$number1`
followed by `"/"` followed by the result of serializing `$number2`.
* Let `quotient` be a number such that:
* Its value is the result of dividing `$number1`'s value by `$number2`'s
value.
* Its value is the result of `divide($number1.value, $number2.value)` as defined
by [IEEE 754 2019], §5.4.1.
* Its numerator units are equal to `$number1`'s numerator units followed by
`$number2`'s denominator units.
* Its denominator units are equal to `$number1`'s denominator units followed
@ -493,10 +442,10 @@ This function is also available as a global function named `random()`.
> Example: `math.random() => 0.1337001337`
* If `$limit` is an **integer** [number] greater than zero:
* If `$limit` is an [integer] greater than zero:
* Return a pseudo-random integer in the range `[1, $limit]` with the same
[units] as `$limit`.
units as `$limit`.
> Examples:
> - `math.random(123) => 87`
@ -505,5 +454,4 @@ This function is also available as a global function named `random()`.
* Otherwise throw an error.
[number]: https://sass-lang.com/documentation/values/numbers
[units]: https://sass-lang.com/documentation/values/numbers#units
[integer]: ../types/number.md#integer

View File

@ -3,16 +3,49 @@
## Table of Contents
* [Definitions](#definitions)
* [Double](#double)
* [Conversion Factors](#conversion-factors)
* [Set of Units](#set-of-units)
* [Compatible Units](#compatible-units)
* [Possibly-Compatible Units](#possibly-compatible-units)
* [Possibly-Compatible Numbers](#possibly-compatible-numbers)
* [Fuzzy Equality](#fuzzy-equality)
* [Integer](#integer)
* [Potentially Slash-Separated Number](#potentially-slash-separated-number)
* [Types](#types)
* [Operations](#operations)
* [Equality](#equality)
* [Greater Than or Equal To](#greater-than-or-equal-to)
* [Less Than or Equal To](#less-than-or-equal-to)
* [Greater Than](#greater-than)
* [Less Than](#less-than)
* [Addition](#addition)
* [Subtraction](#subtraction)
* [Multiplication](#multiplication)
* [Modulo](#modulo)
* [Negation](#negation)
* [Procedures](#procedures)
* [Converting a Number to a Unit](#converting-a-number-to-a-unit)
* [Matching Two Numbers' Units](#matching-two-numbers-units)
* [Simplifying a Number](#simplifying-a-number)
## Definitions
### Double
A *double* is a floating-point datum representable in a format with
* `b = 2`
* `p = 53`
* `emax = 1023`
as defined by [IEEE 754 2019], §3.2-3.3.
[IEEE 754 2019]: https://ieeexplore.ieee.org/document/8766229
> This is the standard 64-bit floating point representation, defined as
> `binary64` in [IEEE 754 2019], §3.6.
### Conversion Factors
Certain units have conversion factors that define how they can be converted to
@ -48,22 +81,36 @@ The following conversion factors are defined:
* `dpi`: 1/96 `dppx`
* `dpcm`: 2.54/96 `dppx`
### Set of Units
A *set of units* is structure with:
* A list of strings called "numerator units".
* A list of strings called "denominator units".
When not otherwise specified, a single unit refers to numerator units containing
only that unit and empty denominator units.
### Compatible Units
Two numbers' units are said to be *compatible* if:
Two numbers' units are said to be *compatible* if both:
* there's a one-to-one mapping between those numbers' numerator units such
that each pair of units is either identical, or both units have a [conversion
factor] and those two conversion factors have the same unit; and
* There's a one-to-one mapping between those numbers' numerator units such that
each pair of units is either identical, or both units have a [conversion
factor] and those two conversion factors have the same unit. This mapping is
known as the numbers' *numerator compatibility map*.
* there's the same type of mapping between those numbers' denominator units.
* There's the same type of mapping between those numbers' denominator units.
This mapping is known as the numbers' *denominator compatibility map*.
[conversion factors]: #conversion-factors
[conversion factor]: ../spec/types/number.md#conversion-factors
Similarly, a number is *compatible with* a set of units if it's compatible with
a number that has those units; and two sets of units are *compatible* if a
Similarly, a number is *compatible with* a [set of units] if it's compatible
with a number that has those units; and two sets of units are *compatible* if a
number with one set is compatible with a number with the other.
[set of units]: #set-of-units
### Possibly-Compatible Units
Two units are *possibly-compatible* with one another if and only if either both
@ -105,6 +152,34 @@ Two numbers are *definitely-incompatible* if they are not possibly-compatible.
> complex units, but in practice these numbers are already flagged as errors
> prior to any possible-compatibility checks.
### Fuzzy Equality
Two [doubles] are said to be *fuzzy equal* to one another if either:
[doubles]: #double
* They are equal according to the `compareQuietEqual` predicate as defined
by [IEEE 754 2019], §5.11.
* They are both finite numbers and the mathematical numbers they represent
produce the same value when rounded to the nearest 1e⁻¹¹ (with ties away from
zero).
### Integer
A SassScript number `n` is said to be an *integer* if there exists a
mathematical integer `m` with an exact [double] representation and `n`'s value
[fuzzy equals] that double.
If `m` exists, we say that `n`'s *integer value* is the double that represents
`m`.
[double]: #double
[fuzzy equals]: #fuzzy-equality
> To avoid ambiguity, specification text will generally use the term
> "mathematical integer" when referring to the abstract mathematical objects.
### Potentially Slash-Separated Number
A Sass number may be *potentially slash-separated*. If it is, it is associated
@ -155,32 +230,208 @@ value, it is written as the original numerator followed by `/` followed by the
original denominator. If either the original numerator or denominator are
themselves slash-separated, they're also written this way.
## Types
The value type known as a *number* has three components:
* A [double] called its "value".
* A list of strings called *numerator units*.
* A list of strings called *denominator units*.
[double]: #doubles
Several shorthands exist when referring to numbers:
* A number's *units* refers to the [set of units] containing its numerator units
and denominator units.
* A number is *unitless* if its numerator and denominator units are both empty.
* A number is *in a given unit* (such as "in `px`") if it has that unit as its
single numerator unit and has no denominator units.
### Operations
#### Equality
Let `n1` and `n2` be two numbers. To determine `n1 == n2`:
* Let `c1` and `c2` be the result of [matching units] for `n1` and `n2`. If this
throws an error, return false.
[matching units]: #matching-two-numbers-units
* Return true if `c1`'s value [fuzzy equals] `c2`'s and false otherwise.
#### Greater Than or Equal To
Let `n1` and `n2` be two numbers. To determine `n1 >= n2`:
* Let `c1` and `c2` be the result of [matching units] for `n1` and `n2` allowing
unitless.
* Return true if `c1`'s value [fuzzy equals] `c2`'s, or if
`compareQuietGreaterEqual(c1.value, c2.value)` returns `true` as defined by
[IEEE 754 2019], §5.11. Return false otherwise.
#### Less Than or Equal To
Let `n1` and `n2` be two numbers. To determine `n1 <= n2`:
* Let `c1` and `c2` be the result of [matching units] for `n1` and `n2` allowing
unitless.
* Return true if `c1`'s value [fuzzy equals] `c2`'s, or if
`compareQuietLessEqual(c1.value, c2.value)` returns `true` as defined by [IEEE
754 2019], §5.11. Return false otherwise.
#### Greater Than
Let `n1` and `n2` be two numbers. To determine `n1 > n2`, return `n1 >= n2 and
n1 != n2`.
#### Less Than
Let `n1` and `n2` be two numbers. To determine `n1 < n2`, return `n1 <= n2 and
n1 != n2`.
#### Addition
Let `n1` and `n2` be two numbers. To determine `n1 + n2`:
* Let `c1` and `c2` be the result of [matching units] for `n1` and `n2` allowing
unitless.
* Return a number whose value is the result of `addition(c1.value, c2.value)` as defined by
[IEEE 754 2019], §5.4.1; and whose units are the same as `c1`'s.
#### Subtraction
Let `n1` and `n2` be two numbers. To determine `n1 - n2`:
* Let `c1` and `c2` be the result of [matching units] for `n1` and `n2` allowing
unitless.
* Return a number whose value is the result of `subtraction(c1.value, c2.value)`
as defined by [IEEE 754 2019], §5.4.1; and whose units are the same as `c1`'s.
#### Multiplication
Let `n1` and `n2` be two numbers. To determine `n1 * n2`:
* Let `product` be a number whose value is the result of
`multiplication(n1.value, n2.value)` as defined by [IEEE 754 2019], §5.4.1;
whose numerator units are the concatenation of `n1`'s and `n2`'s numerator
units; and whose denominator units are the concatenation of `n1`'s and `n2`'s
denominator units.
* Return the result of [simplifying] `product`.
[simplifying]: #simplifying-a-number
#### Modulo
Let `n1` and `n2` be two numbers. To determine `n1 % n2`:
* Let `c1` and `c2` be the result of [matching units] for `n1` and `n2` allowing
unitless.
* Let `remainder` be a number whose value is the result of `remainder(c1.value,
c2.value)` as defined by [IEEE 754 2019], §5.3.1; and whose units are the same
as `c1`'s.
* If `c2`'s value is less than 0 and `remainder`'s value isn't `0` or `-0`,
return `result - c2`.
> This is known as [floored division]. It differs from the standard IEEE 754
> specification because it was originally inherited from Ruby when that was
> used for Sass's original implementation.
>
> [floored division]: https://en.wikipedia.org/wiki/Modulo_operation#Variants_of_the_definition
>
> Note: These comparisons are not the same as `c2 < 0` or `remainder == 0`,
> because they don't do fuzzy equality.
* Otherwise, return `result`.
#### Negation
Let `number` be a number. To determine `-number`, return a number whose value is
the result of `negate(number)` as defined by [IEEE 754 2019], §5.5.1; and whose
units are the same as `number`'s.
## Procedures
### Converting a Number to a Unit
This algorithm takes a SassScript number `number` and a unit `unit`. It's
written "convert `number` to `unit`" or "convert `number` to `unit` allowing
unitless". It returns a number with the given unit. The `unit` argument must
have a conversion factor.
This algorithm takes a SassScript number `number` and a [set of units] `units`.
It returns a number with the given units. It's written "convert `number` to
`units`" or "convert `number` to `units` allowing unitless".
* If `number` has no units:
* If `number` is unitless and this procedure allows unitless, return
`number` with `units`.
* If this procedure allows unitless, return `number` with unit `unit`.
* Otherwise, throw an error.
* If `number`'s unit is `unit`, return `number` as-is.
* Let `number-factor` be `number`'s unit's conversion factor. If `number`
doesn't have a unit or that unit doesn't have a conversion factor, throw an
* Otherwise, if `number`'s units aren't [compatible with] `units`, throw an
error.
* Let `unit-factor` be `unit`'s conversion factor.
[compatible with]: #compatible-units
* If `number-factor` and `unit-factor` don't have the same unit, throw an error.
* Let `value` be `number`'s value.
* Let `result` be the result of multiplying `number`'s numeric component by
`number-factor`'s, then dividing by `unit-factor`'s numeric component.
* For each pair of units `u1`, `u2` in the [numerator compatibility
map] between `number` and `units` such that `u1 != u2`:
* Return `result` with unit `unit`.
[numerator compatibility map]: #compatible-units
* Let `v1` and `v2` be the values of `u1` and `u2`'s [conversion factors].
[conversion factors]: ../spec/types/number.md#conversion-factors
* Set `value` to `division(multiplication(value, v1), v2)` as defined by
[IEEE 754 2019], §5.4.1.
* For each pair of units `u1`, `u2` in the [denominator compatibility map]
between `number` and `units` such that `u1 != u2`:
* Let `v1` and `v2` be the values of `u1` and `u2`'s [conversion factors].
* Set `value` to `division(multiplication(value, v2), v1)` as defined by
[IEEE 754 2019], §5.4.1.
* Return a number with value `value` and units `units`.
### Matching Two Numbers' Units
This algorithm takes two SassScript numbers `n1` and `n2` and returns two
numbers. It's written "match units for `n1` and `n2`" or "match units for `n1`
and `n2` allowing unitless".
* If `n1` is unitless and this procedure allows unitless, return `n1`
with the same units as `n2` and `n2`.
* Otherwise, if `n2` is unitless and this procedure allows unitless, return `n1`
and `n2` with the same units as `n1`.
* Return `n1` and the result of [converting `n2` to `n1`'s units].
[converting `n1` to `n2`'s units]: #converting-a-number-to-units
### Simplifying a Number
This algorithm takes a SassScript number `number` and returns an equivalent
number with simplified units.
* Let `mapping` be a one-to-one mapping between `number`'s numerator units and
its denominator units such that each pair of units is either identical, or
both units have a [conversion factor] and those two conversion factors have
the same unit.
* Let `newUnits` be a copy of `number`'s units without any of the units in
`mapping`.
> `newUnits` for `1px*px/px` is `px`, because only one of the numerator `px`
> is included in the mapping.
* Return the result of [converting `number` to `newUnits`].
[converting `number` to `newUnits`]: #converting-a-number-to-units