sass/accepted/more-math-functions.md

439 lines
13 KiB
Markdown
Raw Normal View History

# More Math Functions: Draft 2.2
*[(Issue)](https://github.com/sass/sass/issues/851)*
This proposal adds the following members to the built-in `sass:math` module.
## Table of Contents
2023-08-22 21:10:27 +00:00
* [Background](#background)
* [Summary](#summary)
2019-12-20 01:14:54 +00:00
* [Semantics](#semantics)
* [Built-in Module Variables](#built-in-module-variables)
* [Variables](#variables)
* [`$e`](#e)
* [`$pi`](#pi)
* [Functions](#functions)
* [`clamp()`](#clamp)
* [`hypot()`](#hypot)
* [Exponentiation](#exponentiation)
* [`log()`](#log)
* [`pow()`](#pow)
* [`sqrt()`](#sqrt)
* [Trigonometry](#trigonometry)
* [`cos()`](#cos)
* [`sin()`](#sin)
* [`tan()`](#tan)
* [`acos()`](#acos)
* [`asin()`](#asin)
* [`atan()`](#atan)
* [`atan2()`](#atan2)
* [Edge cases](#edge-cases)
## Background
> This section is non-normative.
Sass recently implemented a module system with a new built-in `sass:math`
module. The demand for built-in math functions can now be fulfilled safely by
implementing them inside this module. None of these new functions will be made
available on the global namespace.
## Summary
> This section is non-normative.
This proposal defines Sassified versions of all the mathematical functions in
the [CSS Values and Units 4 Draft][], as well as logarithms and the constants
`e` and `pi`. Each function is basically equivalent to its mathematical form,
with stricter unit handling. Proper unit handling prevents these functions from
2019-12-03 19:09:54 +00:00
creating meaningless units. For instance, consider `(1px)^(1/3)`—what does
the unit `px^(1/3)` mean?
[CSS Values and Units 4 Draft]: https://drafts.csswg.org/css-values-4/#math
To avoid issues like this, the exponential functions—`log()`, `pow()`, `sqrt()`
accept only a unitless number as input, and output a unitless number.
The trig functions—`cos()`, `sin()`, `tan()`—accept a SassScript number with a
unit, as long as that unit is an [angle][] type. If the input is a unitless
number, it is treated as though it were in `rad`. These functions output a
unitless number.
[angle]: https://drafts.csswg.org/css-values-4/#angles
The inverse trig functions—`acos()`, `asin()`, `atan()`—accept a unitless number
2019-12-20 01:14:54 +00:00
and output a SassScript number in `deg`. `atan2()` is similar, but it accepts
two unitless numbers.
`clamp()` accepts three SassScript numbers with [compatible][] units: the
minimum value, preferred value, and maximum value. This function "clamps" the
preferred value in between the minimum and maximum values, while preserving
their units appropriately. For example, `clamp(1in, 15cm, 12in)` outputs `15cm`,
whereas `clamp(1in, 1cm, 12in)` outputs `1in`.
[compatible]: ../spec/built-in-modules/math.md#compatible
`hypot()` accepts `n` SassScript numbers with compatible units, and outputs the
length of the `n`-dimensional vector that has components equal to each of the
inputs. Since the inputs' units may all be different, the output takes the unit
of the first input.
2019-12-20 01:14:54 +00:00
## Semantics
### Built-in Module Variables
Variables defined in built-in modules are not modifiable. As such, this proposal
modifies the semantics of [Executing a Variable Declaration][] within the
[Variables spec][] to read as follows:
[Executing a Variable Declaration]: ../spec/variables.md#executing-a-variable-declaration
[Variables spec]: ../spec/variables.md
To execute a `VariableDeclaration` `declaration`:
* Let `value` be the result of evaluating `declaration`'s `Expression`.
* Let `name` be `declaration`'s `Variable`.
* **Let `resolved` be the result of [resolving a variable][] named `name`.**
[resolving a variable]: ../spec/modules.md#resolving-a-member
* If `name` is a `NamespacedVariable` and `declaration` has a `!global` flag,
throw an error.
* **Otherwise, if `resolved` is a variable from a built-in module, throw an
error.**
* Otherwise, if `declaration` is outside of any block of statements, *or*
`declaration` has a `!global` flag, *or* `name` is a `NamespacedVariable`:
* ~~Let `resolved` be the result of [resolving a variable][] named `name` using
`file`, `uses`, and `import`.~~
(...)
* Otherwise, if `declaration` is within one or more blocks associated with
`@if`, `@each`, `@for`, and/or `@while` rules *and no other blocks*:
* ~~Let `resolved` be the result of [resolving a variable][] named `name`.~~
(...)
* ~~Otherwise, if no block containing `declaration` has a [scope][] with a
variable named `name`, set the innermost block's scope's variable `name` to
`value`.~~
[scope]: ../spec/spec.md#scope
2019-12-20 01:14:54 +00:00
* **Otherwise, if `resolved` is null, get the innermost block containing
`declaration` and set its scope's variable `name` to `value`.**
* ~~Otherwise, let `scope` be the scope of the innermost block such that `scope`
already has a variable named `name`.~~
* **Otherwise, set `resolved`'s value to `value`.**
## Variables
### `$e`
Equal to the value of the mathematical constant `e` with a precision of 10
2019-12-20 01:14:54 +00:00
digits after the decimal point: `2.7182818285`.
### `$pi`
Equal to the value of the mathematical constant `pi` with a precision of 10
2019-12-20 01:14:54 +00:00
digits after the decimal point: `3.1415926536`.
## Functions
### `clamp()`
```
clamp($min, $number, $max)
```
* If the units of `$min`, `$number`, and `$max` are not compatible with each
other, throw an error.
2019-12-20 01:14:54 +00:00
* If some arguments have units and some do not, throw an error.
* If `$min >= $max`, return `$min`.
* If `$number <= $min`, return `$min`.
* If `$number >= $max`, return `$max`.
* Return `$number`.
### `hypot()`
```
hypot($numbers...)
```
* If all numbers are not compatible with each other, throw an error.
* If some numbers have units and some do not, throw an error.
* If all numbers are unitless, the return value is unitless.
* Otherwise, the return value takes the unit of the leftmost number.
* If any number equals `Infinity` or `-Infinity`, return `Infinity`.
* Return the square root of the sum of the squares of each number.
### Exponentiation
#### `log()`
```
log($number, $base: null)
```
2019-12-20 01:14:54 +00:00
* If `$number` has units, throw an error.
* If `$base` is null:
2019-12-20 01:14:54 +00:00
* 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
#### `pow()`
```
pow($base, $exponent)
```
* If `$base` or `$exponent` has units, throw an error.
* If `$exponent == 0`, return `1` as a unitless number.
2019-12-20 01:14:54 +00:00
* 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.
#### `sqrt()`
```
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.
### Trigonometry
#### `cos()`
```
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`.
2019-12-20 01:14:54 +00:00
* If `$number == Infinity` or `$number == -Infinity`, return `NaN` as a unitless
number.
* Return the [cosine][] of `$number`, as a unitless number.
[cosine]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
#### `sin()`
```
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`.
2019-12-20 01:14:54 +00:00
* 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.
[sine]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
#### `tan()`
```
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`.
2019-12-20 01:14:54 +00:00
* 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.
[tangent]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
#### `acos()`
```
acos($number)
```
* If `$number` has units, throw an error.
2019-12-20 01:14:54 +00:00
* 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
#### `asin()`
```
asin($number)
```
* If `$number` has units, throw an error.
2019-12-20 01:14:54 +00:00
* If `$number < -1` or `$number > 1`, return `NaN` as a number in `deg`.
* If `$number == -0`, return `-0deg`.
* If `$number == 0`, return `0deg`.
2019-12-20 01:14:54 +00:00
* Return the [arcsine][] of `$number`, as a number in `deg`.
[arcsine]: https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Basic_properties
#### `atan()`
```
atan($number)
```
* If `$number` has units, throw an error.
2019-12-20 01:14:54 +00:00
* If `$number == -0`, return `-0deg`.
* If `$number == 0`, return `0deg`.
2019-12-20 01:14:54 +00:00
* 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
#### `atan2()`
> `atan2($y, $x)` is distinct from `atan($y / $x)` because it preserves the
> quadrant of the point in question. For example, `atan2(1, -1)` corresponds to
2019-12-20 01:14:54 +00:00
> the point `(-1, 1)` and returns `135deg`. In contrast, `atan(1 / -1)` and
> `atan(-1 / 1)` resolve first to `atan(-1)`, so both return `-45deg`.
```
atan2($y, $x)
```
* If `$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
2019-12-20 01:14:54 +00:00
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
##### 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>
2019-12-20 01:14:54 +00:00
<td>-135deg</td>
<td>-90deg</td>
<td>-90deg</td>
<td>-90deg</td>
<td>-90deg</td>
<td>-45deg</td>
</tr>
<tr>
<th>-finite</th>
2019-12-20 01:14:54 +00:00
<td>-180deg</td>
<td></td>
2019-12-20 01:14:54 +00:00
<td>-90deg</td>
<td>-90deg</td>
<td></td>
2019-12-20 01:14:54 +00:00
<td>-0deg</td>
</tr>
<tr>
<th>-0</th>
2019-12-20 01:14:54 +00:00
<td>-180deg</td>
<td>-180deg</td>
<td>-180deg</td>
<td>-0deg</td>
<td>-0deg</td>
<td>-0deg</td>
</tr>
<tr>
<th>0</th>
2019-12-20 01:14:54 +00:00
<td>180deg</td>
<td>180deg</td>
<td>180deg</td>
<td>0deg</td>
<td>0deg</td>
<td>0deg</td>
</tr>
<tr>
<th>finite</th>
2019-12-20 01:14:54 +00:00
<td>180deg</td>
<td></td>
2019-12-20 01:14:54 +00:00
<td>90deg</td>
<td>90deg</td>
<td></td>
2019-12-20 01:14:54 +00:00
<td>0deg</td>
</tr>
<tr>
<th>Infinity</th>
2019-12-20 01:14:54 +00:00
<td>135deg</td>
<td>90deg</td>
<td>90deg</td>
<td>90deg</td>
<td>90deg</td>
<td>45deg</td>
</tr>
</tbody>
</table>