Closed tabatkins closed 4 years ago
The CSS Working Group just discussed the rest of the JS Math functions
, and agreed to the following:
RESOLVED: add these 5 - abs, log, exp, and the two constants e and pi
The CSS Working Group just discussed Adding an infinity keyword
, and agreed to the following:
RESOLVED: no non-ASCII in Core CSS
RESOLVED: add infinity and nan as new css math constants
log1p()
, expm1()
etc. are recommended in IEEE 754, which also suggests stuff like sinPi(x) = sin(x*PI)
.
In #309, I argued for a new angular unit pi
or pirad
. Exponential notation inherited from SVG makes the letter e unfit to be used as a unit, but it is required way less often than π or τ in planar geometry, which is the scope of CSS.
I believe Eulerʼs number would be fine as exp(1)
, which would be available anyway and is shorter than const(e)
, calc(e)
or math(e)
. It would be strange to have e()
as well, whereas a single-letter keyword would be fine.
<dbaron> Oriol was pointing out that cbrt(-8) is -2 whereas pow(-8, 1/3) is NaN <dbaron> And, particularly, fractions 1/N where N is odd are not representable exactly unless N is 1 or -1.
How was this not convincing enough to also add cbrt()
or, alternatively, inv()
for 1/x to get pow(-8, inv(3)) = -2
?
I've added everything in 90ff717f and the several preceding commits.
log1p(), expm1() etc. are recommended in IEEE 754, which also suggests stuff like sinPi(x) = sin(x*PI).
Yeah, special-cases for those can be useful for increased precision and faster calculation in potentially perf-sensitive locations. Neither of those are particularly relevant in CSS.
In #309, I argued for a new angular unit pi or pirad.
We've added the keyword pi
now. You do need to do standard calc math at it: calc(2 * pi)
, not calc(2pi)
. (Using a unit was discussed, but it was rejected for a few reasons: it would be breaking new ground to have a unit which resolves to a <number>
; it would be inconsistent with e
due to the exp-notation clash you mentioned; in general it's a bit too clever of a hack.)
I believe Eulerʼs number would be fine as exp(1), which would be available anyway and is shorter than const(e), calc(e) or math(e). It would be strange to have e() as well, whereas a single-letter keyword would be fine.
exp(1)
is shorter than calc(e)
, yes, but calc(exp(1) - 1)
is longer than calc(e - 1)
. Given that using e directly as a value is something I expect to be quite odd and rare, I don't think we need to optimize for that.
How was this not convincing enough to also add cbrt() or, alternatively, inv() for 1/x to get pow(-8, inv(3)) = -2?
We don't have any particular use-cases for cube-rooting negative numbers right now. Note that its addition is still open if someone comes up with a sufficiently convincing use-case, either showing that cube-rooting in general is common enough to be worth a special-case, or showing that cube roots of negative numbers in particular are useful in reasonable scenarios (probably a lower threshold needed than the previous).
Something like inv() wouldn't really work; the model we're using right now still assumes that subtrees can be collapsed to numeric values representable in a float, double, or similar representation. Explicit rationals are out of scope currently. If we did decide this was worthwhile, we could do it with a root()
function, that could then have the desired behavior for odd integer roots.
Here's the list of all the properties on the JS
Math
object:I've crossed out the things we already have in the spec. Of the leftovers, I think we can break them into a few categories:
General Algebra
I'm happy to add sign() (#4673). I'd be fine with adding cbrt() too; it's lower-value than sqrt(), but sure, might as well. abs() can be done with single-argument hypot(), but might as well have the better-named version as well. So these are all plausible additions.
Hyperbolic Trig
I've never had to use the hyperbolic functions in my life. If there's a reasonable argument for them being useful, I'm fine with adding them, but until then I consider these something we can safely skip.
Logs
x=>Math.log(1+x)
)x=>Math.exp(x) - 1
)In general I think we could add these reasonably. exp() is currently handleable with pow(), if you write in e explicitly, but that's clumsy.
Logs seem useful, but we should just have a single function with an optional second arg for the base, rather than introduce three differently-named variants. Other than precision issues,
log2(x)
is exactly equivalent tolog(x) / log(2)
, after all.I don't think we should add log1p() and expm1(); I suspect they're just things we pulled in from Java? You can just use math, dang:
log(1 + x)
andcalc(exp(x) - 1)
.So in conclusion I think
log(val, base?)
(with base defaulting to e) andexp()
are reasonable to add.Rounding
Covered in #2513. Current proposal is to add a round() function, with keywords distinguishing between ceil/floor/round/trunc behavior. fround() is ignored due to lack of use-cases.
Constants
Pi and E might have reasonable cases. E can be written as
exp(1)
, which might be reasonable to just stick with (with a note calling it out). Pi iscalc(2 * acos(0))
, or a few other possibilities, which is a lot less reasonable, but a lot of places that need Pi take angles already. Unclear if it's needed or not.If we did add them, I guess they'd be added as zero-arg functions
e()
andpi()
? Weird, but at least functions are already fine grammatically in calc expressions.The rest of the constants are just existing easy expressions, with their values baked into constants for speed in hot loops. I don't think we need them.
Stuff We Probably Don't Want
random() can't be done; it's a totally different topic.
clz32() (round to a 32-bit integer, count number of leading zeros in the binary representation) and imul() (do a C-like 32-bit integer multiply) are both odd things that I don't think we have any usecases for.
So in conclusion, beyond the things already discussed in #4673 (sign()) and #2513 (round()), we might want to add:
...and then we're done, I think.