Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-values] Allow division of same types in calc() #545

Closed
LeaVerou opened this issue Sep 28, 2016 · 16 comments
Closed

[css-values] Allow division of same types in calc() #545

LeaVerou opened this issue Sep 28, 2016 · 16 comments
Labels
css-values-4 Current Work

Comments

@LeaVerou
Copy link
Member

LeaVerou commented Sep 28, 2016

The reason that division by a <length> was originally not allowed was that in some cases there was no way of knowing whether that was a division by 0, and before Variables there was no such concept as invalid in computed value time. Now that there is, perhaps we can revisit this issue for Values 4.

This is especially useful in conjunction with CSS Variables, as currently one can convert from a <number> to a <length> (or <angle> etc) but there is no way to convert a <length> to a <number>! Also, calculating the ratio of two lengths is often very useful, e.g. what percentage of the viewport width is 500px? calc(500px / 100vw)! Not to mention that this would be a more consistent, less surprising design.

@LeaVerou LeaVerou changed the title [css-values] Allow division by <length> in calc() [css-values] Allow division of same types in calc() Sep 28, 2016
@dauwhe dauwhe added the css-values-4 Current Work label Sep 28, 2016
@LeaVerou
Copy link
Member Author

LeaVerou commented Oct 4, 2016

@jpamental and I just found another use case: Variable fonts that smoothly change width and/or weight as the viewport is resized via dividing a length with viewport units.

@jpamental
Copy link

Seems like @tabatkins is talking about similar math issues here: #581

@fantasai
Copy link
Collaborator

Yeah, this was in the plans for Level 4 (since it was deferred out of L3).

CSSWG just resolved to add it back to L4, so just needs edits at this point.

@tabatkins
Copy link
Member

Fixed in ea66912 and f791dfa.

Agenda+ for review by the WG.

@dbaron
Copy link
Member

dbaron commented May 24, 2018

For the record, one place (out of possibly more than one) where the group resolved to add unit algebra to calc() was the 2014-04-23 teleconference.

@dbaron
Copy link
Member

dbaron commented May 24, 2018

I think my only review comments, after reviewing 8.1.2. Type Checking are:

  • the definition of division seems a little bit on the informal side -- "negate" / "negated" isn't formally defined, and I suppose it's not completely clear what it would mean for the percent hint on a type. (Can the type in a calc() ever have a percent hint?)
  • it's not clear what determines whether the result is <number> or <integer>, though I suspect that's a pre-existing issue.
  • I'm worried that the clamping behavior (for infinities) is also underdefined.

@css-meeting-bot
Copy link
Member

The Working Group just discussed Allow division of same types in calc().

The full IRC log of that discussion <dael> Topic: Allow division of same types in calc()
<dael> github: https://github.com//issues/545
<dael> astearns: Looks like there's a change for this
<dael> TabAtkins: I think all that's left is...I wanted review by WG
<dael> TabAtkins: I added the resolution from months ago to allow unit algebra in calc(). dbaron gave me review which I hadn't seen yet. I'll resolve those. Any other comments or concerns please let me know. It's i n the spec
<dael> frremy: I'm wondering how you decided the final unit value of a calc. You prob have to back track. I think we have to change how we do this. Seems fine by me, but it requires keeping track of more things
<dael> TabAtkins: Yep, how to determine type it resolves to is in the spec and you need the algo to do Typed OM. I leaned on Typed OM as much as possible b/c I think I got it right
<dael> chris: This is the correct thing to do. It's compat with typed OM we should do this.
<dael> astearns: This edits is a result of a resolution?
<dael> TabAtkins: Yes. We've had multiple resolutions to do this. dbaron found one in 2014. this is just a review request.
<chris> 2014, wow
<dael> astearns: Sounds like dbaron looked. chris and frremy are okay. I think we're good. Anyone else wanting to look please do.

@astearns astearns removed the Agenda+ label May 30, 2018
@AmeliaBR
Copy link
Contributor

I agree with @dbaron that "negating" types is confusing wording.

it's not clear what determines whether the result is <number> or <integer>, though I suspect that's a pre-existing issue.

Based on the decision in #2337, there is no longer any need to keep track of the distinction between numbers and integers: the calculation can happen as full numbers, and integer clamping happen on the final value.

More comments on @tabatkins' edits:

  • Which is likely to go to CR first, Values 4 or CSS Typed OM 1? Maybe it would be better to move the full type checking definitions here, and then reference/overwrite them from Typed OM.

  • Regardless of whether or not you continue to link to Typed OM, it would be helpful if you actually define the type map concept in this spec before using it in the list of rules. Suggested wording:

    Operators form sub-expressions, which can have complex types created by combining the types of their arguments. These complex types are represented as map of base types to integers representing a power.

    [example]
    The complex type «[ "length" → 1 ]» is a simple length (to the power 1).

    The complex type «[ "length" → 2 ]» is a length squared (to the power 2), which could represent an area measurement such as cm2 (although area measurements aren't currently used in CSS).

    The complex type «[ "length" → 1, "time"→ -1 ]» is length over time (length to the power 1, time to the power negative 1), which could represent speed such as cm/s (again, not a type currently used in CSS properties, but still valid as an intermediary type in a calculation).

    The complex type «[ "length" → 0 ]» is the same as «[ ]» (an empty type map), and is equivalent to a unitless number.

  • The "In previous versions..." sentence should be pulled out as an informative note.

  • Then, in the rule list, move the list of simple types up to be the first point in the list, as it provides an understandable example of what this means.

  • Since that bullet point already handles converting percentages to their "hint" types, I think it just confuses matters to link to the Houdini "add the types" algorithm for the "+" operator. In this simplified case, the algorithm is:

    At a + or - sub-expression, determine simplified type maps for both the left and right arguments, by calculating a complex type map for that argument and then removing any types with a power of 0.
    If the resulting type maps for both arguments are not identical, then the entire math function is invalid.
    Otherwise, the sub-expression's type map is the result of removing any types with a power of 0 from the complex type map of one of its arguments.

  • For multiplication: again, the linked Houdini algorithm includes complications about percent hints which don't apply here. Notably, with percent hints already resolved to other base types, there is no way for the type checking to fail in a multiplication operation. After removing percent hints from the algorithm, and converting some of the pseudocode to prose, it would look something like:

    At a * sub-expression, generate a new complex type map, finalType, that is a copy of the complex type map for the left-hand argument to the expression.
    Then, for each base type in the complex type map of the right-hand argument:

    • if finalType already has an entry for that base type, increment its power by the amount of the power from the right-hand type map;
    • otherwise, add a new entry for that base type to finalType, with a power equal to the power from the right-hand type map.

    The sub-expression’s type is finalType.

  • For division:

    At a / sub-expression, invert the type of the right-hand argument by multiplying ever power in the complex type map by -1.
    The sub-expression's type is the result of multiplying the left-hand argument by this inverted type.

  • If the decision is that this spec should stand alone, without reference to Typed OM, then the paragraph about resolving the math expression to a final type also needs to be re-written. But even if you continue to link to Typed OM, there might need to be some clarification here given that percentage types have already been resolved to other base types when possible. (The rules in Typed OM do not define mutually exclusive types, since they allow a «[ "percent" → 1 ]» type to match percent, length-percent, angle-percent, etc.)

  • Per the decision in [css-typed-om] 'fr' units cannot be used in a calc css-houdini-drafts#734 (comment), the "anything else" bullet point will need to be updated (although it might be best to keep that as a separate commit).

  • The note about percentages and numbers not being combine-able needs to be updated, so that it doesn't reference division by other types being disallowed.

@tabatkins
Copy link
Member

tabatkins commented May 31, 2018

the definition of division seems a little bit on the informal side -- "negate" / "negated" isn't formally defined, and I suppose it's not completely clear what it would mean for the percent hint on a type. (Can the type in a calc() ever have a percent hint?)

Sigh, I knew that would be problematic, but hoped it was okay to be short with it. I added an "invert a type" algo to TypedOM and am using it directly now.

it's not clear what determines whether the result is <number> or <integer>, though I suspect that's a pre-existing issue.

As Amelia said, that distinction is no longer necessary.

I'm worried that the clamping behavior (for infinities) is also underdefined.

Interesting, what problems do you foresee? It should be identical to the existing clamping behavior for very-large-but-finite values; I can't imagine anything that would distinguish it from that case.


Which is likely to go to CR first, Values 4 or CSS Typed OM 1? Maybe it would be better to move the full type checking definitions here, and then reference/overwrite them from Typed OM.

I don't particularly care; if it becomes a publication issue I'll move things, but otherwise I'll just leave them where they are.

Regardless of whether or not you continue to link to Typed OM, it would be helpful if you actually define the type map concept in this spec before using it in the list of rules.

I think that would be a distraction from the spec text. Even in Typed OM, the algorithms for types are off in their own subsection, separated from the actual operations that use them, because the details aren't particularly relevant for understanding things. The fact that following the definitions jumps to another document instead of a same-document separate section isn't a particularly relevant difference here.

The "In previous versions..." sentence should be pulled out as an informative note.

Good catch, done.

Then, in the rule list, move the list of simple types up to be the first point in the list, as it provides an understandable example of what this means.

I see the value of this, but I'm currently relying on it being last so I can say "anything else". For consistency I'd also have to move the final bullet point up, and I think it reads best put at the end.

Since that bullet point already handles converting percentages to their "hint" types, I think it just confuses matters to link to the Houdini "add the types" algorithm for the "+" operator.

Sure, it's some indirection, but it's identical to the type-determining line of the .add() algorithm in TypedOM, and that sort of parallel construction helps highlight that there's nothing surprising. Rewriting it to be more explicit and reproducing the "add two types" algo makes this less obvious.

I'm similarly against trying to inline the algorithms for multiplication and division.

Notably, with percent hints already resolved to other base types, there is no way for the type checking to fail in a multiplication operation.

Ah, this is true. I'll simplify.

(The rules in Typed OM do not define mutually exclusive types, since they allow a «[ "percent" → 1 ]» type to match percent, length-percent, angle-percent, etc.)

They do define mutually-exclusive types among the types that I listed. I didn't say that it can resolve to the <*-percentage> productions on purpose.

Per the decision in w3c/css-houdini-drafts#734 (comment), the "anything else" bullet point will need to be updated (although it might be best to keep that as a separate commit).

I don't understand what needs to be updated - I wrote the spec text with the assumption that the issue would be resolved in the way I wanted, so it already defines how to handle <flex>.

The note about percentages and numbers not being combine-able needs to be updated, so that it doesn't reference division by other types being disallowed.

Updated from future to present tense. ^_^

@dbaron
Copy link
Member

dbaron commented Jun 1, 2018

So for clamping of infinities, a few things (though maybe not what I was thinking at the time):

  • the allowed range doesn't seem to be defined
  • since some calc()s are resolved at computed value time, I think some clamping needs to happen then, or else you need to figure out how to represent infinite computed values
  • do the IEEE-754 semantics depend on the order of operations in places where the order of operations isn't defined by CSS? (I'm not sure.)
  • within what scope does an implementation need to track distinctions like +0 and -0? A single calc() expression? When calc() is nested inside of calc(), is that treated as just syntactic sugar for parentheses, or a separate expression that triggers clamping? (I'd hope the former.) Likewise for "If a math function would resolve to NaN, it instead resolves to positive infinity." which I would hope refers to the entire toplevel function, not pieces of it -- though that should be clear.
  • is it defined how min() and max() handle NaNs?

@tabatkins
Copy link
Member

the allowed range doesn't seem to be defined

It wasn't previously defined either, that's up to individual properties and/or user-agent choice. Again, you could always define an arbitrarily large value in calc(); infinity is just slightly larger than that. ^_^

since some calc()s are resolved at computed value time, I think some clamping needs to happen then, or else you need to figure out how to represent infinite computed values

Ah, I didn't mean to restrict it to used-values only. Fixed.

do the IEEE-754 semantics depend on the order of operations in places where the order of operations isn't defined by CSS? (I'm not sure.)

I dunno either.

within what scope does an implementation need to track distinctions like +0 and -0?

I rewrote it to be more specific, specifying exactly which operations produce infinities or NaNs, and how infinities or NaNs affect operations they show up in. I defined "top-level calculations" to be any of the expression arguments to min()/max()/clamp(), or a non-nested calc(); these censor NaN by turning it into positive infinity, while non-top-level calculations are still allowed to produce NaN. (So in particular, calc(-1 * calc(0/0)) will produce positive infinity, not negative infinity, because the inner calc() resolves to a NaN, so the outer calc() resolves to a NaN, and gets censored into positive infinity.)

I went ahead and dropped negative-zero tracking, tho I'm not sure if I should. There are only two ways to produce a negative zero in 754 semantics: explicitly negate a zero (or an expression resulting in zero), or divide a finite number by negative infinity. calc() can't negate things, only subtract them, and subtraction will never result in -0 unless one of the arguments is -0 already. So we're left with dividing by negative infinity. Unless there's some difficulty with implementations having semantics differenting from 754, I'd rather just pretend that particular case doesn't exist. But if it does become a problem, I'm fine with explicitly specifying how it works, too.

@tabatkins
Copy link
Member

Agenda+ to ask about whether I should keep negative-zero tracking within an expression. (The distinction will disappear when it exits a top-level calculation, at the same time as NaNs disappear.)

Specifically, should calc(1 / calc(1/(-1/0))) produce positive infinity or negative infinity? If negative-zero is tracked, the the inner calc produces negative zero, and the outer calc then produces negative infinity. If it's not, then the inner calc just produces zero, and the outer calc produces positive infinity.

@FremyCompany
Copy link
Contributor

Mmmmm, I have the impression dividing by zero is currently disallowed by all browsers.
Did we have a resolution to change this?

If yes, no opinion, since there isn't an implementation of this yet.

@FremyCompany
Copy link
Contributor

https://wptest.center/#/lr3dp8 (testcase in case someone else want to try this before the call)

@tabatkins
Copy link
Member

Yes, we changed that (because we have to - you can't tell at parse time if 1 / (16px - 1em) will be a division by zero). That's not an issue at hand. ^_^

@css-meeting-bot
Copy link
Member

The Working Group just discussed Allow division of same types in calc() should calc(1 / calc(1/(-1/0))) produce positive infinity or negative infinity?, and agreed to the following:

  • RESOLVED: Allow calc to handle negative 0s
The full IRC log of that discussion <dael> Topic: Allow division of same types in calc() should calc(1 / calc(1/(-1/0))) produce positive infinity or negative infinity?
<dael> github: https://github.com//issues/545#issuecomment-394474713
<dael> TabAtkins: In the corse of proting typed OM into calc, typed OM depends on float semantics but in calc CSS values don't theoretically have a float semantic bound. I have to define certain cases. Division by 0 produses postiive or negative infinity and it's clamped. I followed ieee semantics. ieee754 semantics track positive and negative. Css doesn't normally care about it.
<dael> TabAtkins: Depending on what you're doing you can construct something that resolves to postive or negative infinity. So far for simplicity I've ommited them but it's not hard to add them back in. I suspect we would want that because it's simpler for typed OM and calc to match in these cases. Wanted to verify
<TabAtkins> calc(1 / calc(1/(-1/0)))
<dael> TabAtkins: In issue I have an example ^
<dael> TabAtkins: Resolves differently. It double inverts a negative inifity and will be positive or negative depending on if a negative 0 escapes the inner calc.
<dael> TabAtkins: I propose we track 0 signedness to be consistant with general float semantics.
<dael> fremy: No objections. Still wondering why track inifity. We can say it's not a number so it doesn't matter.
<dael> TabAtkins: If you're approaching a 0 in the denominator of something you don't want to suddenly flip. The infinities have to be something. If they're all positive if something is approaching inifnity it'll flip to positive and go from really big to really small.
<dael> TabAtkins: Typed OM is just math over JS numbers and they have infinities.
<dael> TabAtkins: We could expose extra semantics but it doesn't buy us anything.
<dael> fremy: If you animate the denominator from -1 to 0 when you get to 0 it's positive. There isnt' a browser supporting it so it's not a huge use case.
<dael> TabAtkins: I know it's a new feature. There's no compat to worry about.
<dael> TabAtkins: If all infinities go 0 and you have -1/-1 and then bottom goes to 0 -1/0 should be negative.
<dael> fremy: -1/-1 is positive.
<dael> TabAtkins: Sorry, meant the other way.
<dael> fremy: -1/-1 goes to a positive 0. But okay, it's fine. I'm fine with whatever you want.
<dael> TabAtkins: I'm not making up semantics. I'm saying follow ieee as close as possible.
<TabAtkins> \/kick NegativeInfinity
<dael> AmeliaBR: I agree with TabAtkins that it makes sense to be consistent with standard computer math that's already in JS. Also important b/c CSS we are animmting values and once we're allowing people to divide different lengths like font size/viewport width and animate to 0 this could come up. Continuous animations until something turns infinite makes sense.
<dael> astearns: Other impl opinions?
<dael> astearns: dbaron do you have an opinion?
<dael> dbaron: No strong opinion
<dael> astearns: Other opinions?
<dael> astearns: I'm hearing people call for dealing with negative 0. No opinions against.
<dael> astearns: Objections to allowing calc to handle negative 0s?
<dael> RESOLVED: Allow calc to handle negative 0s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-values-4 Current Work
Projects
None yet
Development

No branches or pull requests

10 participants