Skip to content

[css-values] Let B default to 1 in round(<strategy>?, A, B) #9668

Closed

Description

@Loirooriol

I get the reasoning for requiring a precision in most cases. However, as I mentioned in #2513 (comment), it seems fine to omit it for numbers.

So if B becomes optional, defaulting to 1, it will be possible to do things like

round(2.3) /* 2 */
round(up, 2.3) /* 3 */

while round(2.3em) will continue being invalid due to the type mismatch between A and B.

Activity

  1. SebastianZ commented on Dec 4, 2023

    @SebastianZ
    Contributor

    while round(2.3em) will continue being invalid due to the type mismatch between A and B.

    Does it have to be invalid? Isn't it possible to just assume the same type for B in that case? I.e. round(2.3em) would be equivalent to round(2.3em, 1em).

    Sebastian

  2. Loirooriol commented on Dec 4, 2023

    @Loirooriol
    ContributorAuthor

    No, that would be bad. Let's say 1em = 16px, then round(2.3em) should produce the same as round(36.8px). But round(2.3em, 1em) is 2em = 32px, while round(36.8px, 1px) is 37px. Making it unit-dependent is also not clear for round(2.3em + 2.3px).

    It could work by saying that step defaults to one canonical unit, but in round(2.3em) authors may get surprised it's 1px instead of 1em, so better require it.

  3. tabatkins commented on Dec 4, 2023

    @tabatkins
    Member

    Right, that's precisely why it requires a unit right now - we can't have the unit depend on the input (for the reasons given, plus things like round(1em + 10px) that don't have a single unit), and we don't want to default a mystery unit that might violate author expectations.

    So if B becomes optional, defaulting to 1

    Yes, we could do this. Is there a particular reason to do so, tho?

  4. Loirooriol commented on Dec 4, 2023

    @Loirooriol
    ContributorAuthor

    It just seems strange to require the 1 when working with plain numbers, since it's the obvious choice. Like, in English people just say "round x", not "round x to a multiple of 1".

  5. SebastianZ commented on Dec 6, 2023

    @SebastianZ
    Contributor

    Let's say 1em = 16px, then round(2.3em) should produce the same as round(36.8px).

    Converting the unit before rounding is totally unexpected from an author's standpoint.

    we can't have the unit depend on the input (for the reasons given, plus things like round(1em + 10px) that don't have a single unit), and we don't want to default a mystery unit that might violate author expectations.

    The point was for rounding when only having a single unit. So, round(1em + 10px) would still be invalid. Only round(2.3em), round(42.8deg), or round(87.39%) would be valid (being equivalent to round(2.3em, 1em), round(42.8deg, 1deg), and round(87.39%, 1%), respectively).

    Allowing to omit B for numbers but not dimensions or percentages may cause confusion.

    Like, in English people just say "round x", not "round x to a multiple of 1".

    Not just spoken languages but also also in many programming languages you can omit the precision - or even don't have a way to define one like in JavaScript.

    Sebastian

  6. jfkthame commented on Dec 6, 2023

    @jfkthame
    Contributor

    The point was for rounding when only having a single unit. So, round(1em + 10px) would still be invalid.

    Would round(3.14159em + 2.71828em) be valid, then? The unit is consistent/clear, but it seems like this could still be tricky/confusing. It looks "obvious" what to do when the units are right there, but what about when you have round(--var(foo) + --var(bar)), where foo and bar are both lengths? Whether the round works (and what it means) then depends on how they were defined in this particular context. I think that's getting too obscure and footgun-ish to be useful.

    Making the precision default to 1 (as a number) only, in contrast, is clear and simple, while rounding anything other than a number requires being explicit about the unit of rounding.

  7. tabatkins commented on Dec 6, 2023

    @tabatkins
    Member

    So, round(1em + 10px) would still be invalid. Only round(2.3em), round(42.8deg), or round(87.39%) would be valid (being equivalent to round(2.3em, 1em), round(42.8deg, 1deg), and round(87.39%, 1%), respectively).

    That's pretty fragile, and it means that round(var(--foo)) may or may not be valid, and might round to an unpredictable precision, based on how the value is written somewhere else.

    And round(10em / 3)? Either that's invalid too (and the entire feature starts getting super fragile), or we have to rewrite the simplification rules to maintain units as-is when possible.

    In-flight edit: lol, @jfkthame makes the exact same points.


    Anyway, if impls want to change the defaulting rules to allow a default of 1 for B, I'm fine with that. Agenda+ to discuss it.

  8. Crissov commented on Dec 7, 2023

    @Crissov
    Contributor

    I agree with @SebastianZ and @Loirooriol that it would be unintuitive for authors if the rounding base defaulted to 1<canonical unit>, especially for simple terms like 2.3em, but it might still be the most reasonable and useful solution, because you hardly ever round simple terms.
    You usually need either the result of a calculation rounded with the resulting unit or a value with a relative unit, e.g. em or %, expressed as an integer amount in an absolute unit like px.

    My educated guess is that the most common use case by far will be rounding (calculated) lengths to whole pixels. Since px is the canonical unit there, this would be covered nicely.

    Another viable solution I see would be to treat a <number> given as the second parameter not as a module but as the number of decimal places. Then, round(4.32, 1) would compute to 4.3 instead of 4 and likewise round(4.32em, 1) = 4.3em, while round(4.32em, 1em) would yield 4em. The default value would be 0.

    Finally, instead of defaulting to 1, 1<resulting unit> or 1<canonical unit>, the second parameter could support keywords:

    • round(4.32em, auto) = 4em
    • round(4.32em, default) = e.g. 69px
    • round(4.32em, none) = e.g. 69.12px
  9. tabatkins commented on Dec 8, 2023

    @tabatkins
    Member

    I strongly disagree that rounding to "whole pixels" is the most common case for rounding lengths. I think that's actually an extremely rare case; subpixel lengths are usually just fine to work with. Similarly, rounding to the nearest degree doesn't seem particularly useful for most cases. The canonical unit is quite arbitrary, and generally speaking I don't think people will commonly be rounding to 1 of any unit; the rounding precision depends intimately on what one is actually trying to accomplish.

    the number of decimal places.

    That's just a less powerful version of the current argument, where you're limited to precisions that are a power of 10 of some unit. It also has all the existing problems of a default rounding precision.

    keywords

    These either continue to have the existing problems of a default rounding precision, or are equivalent to just providing a trivial value.

  10. SebastianZ commented on Dec 8, 2023

    @SebastianZ
    Contributor

    That's pretty fragile, and it means that round(var(--foo)) may or may not be valid

    That's true in any case, as --foo can hold anything.

    Though we could also define that the unit of the first operand is used. So round(3.14159em + 2.71828em) = round(3.14159em + 2.71828em, 1em) = 6em, round(1em + 10px) = round(1em + 10px, 1em) = 2em, and round(10px + 1em) = round(10px + 1em, 1px) = 26px (in case 1em = 16px).
    That makes it explainable and less fragile.

    and might round to an unpredictable precision, based on how the value is written somewhere else.

    The precision is not unpredictable, it is unit-based.

    Similarly, rounding to the nearest degree doesn't seem particularly useful for most cases. The canonical unit is quite arbitrary, and generally speaking I don't think people will commonly be rounding to 1 of any unit.

    The point is to provide a reasonable fallback / default. Authors will still be advised to provide a rounding interval if they want to influence the rounding precision.

    Another viable solution I see would be to treat a <number> given as the second parameter not as a module but as the number of decimal places.

    Note that Firefox already ships the round() function. So, a change in behavior will break any current usages.

    Sebastian

  11. Loirooriol commented on Dec 8, 2023

    @Loirooriol
    ContributorAuthor

    Though we could also define that the unit of the first operand is used

    This would violate commutativity. And in fact serialization sorts summation operands alphabetically by their units, so this wouldn't roundtrip. And anyways it's seems very unintuitive to me.

  12. tabatkins commented on Dec 8, 2023

    @tabatkins
    Member

    Ultimately, trying to infer a rounding precision from the source unit is going to fail in lots of cases, and even when it works, there's zero guarantee that it'll produce a useful value.

    The current spec makes it trivial to round to any unit you like (round(..., 1em)) and to any size at all, and doesn't need to do any fragile or unexpected inference at all. It's really unlikely we'd ever change it to be more complicated and fragile, for what is almost certainly a small minority use-case of "people who want to round to 1px/1deg/etc".

  13. Crissov commented on Dec 9, 2023

    @Crissov
    Contributor

    It just occurred to me that with keywords in the second parameter, round() could also be a place to implement truly physical length measurements. #5986 #614

    Either round(100mm, physical) or round(100mm, millimeter) would give the best approximation of 1 dm the browser can do with the active output device.

    However, authors would probably want arbitrary precision there, which would be crude to support with keywords and always remain incomplete. Treat this as a devil’s advocate proposal then.

  14. SebastianZ commented on Dec 9, 2023

    @SebastianZ
    Contributor

    It just occurred to me that with keywords in the second parameter, round() could also be a place to implement truly physical length measurements. #5986 #614

    That's out of the scope of this issue, which is to make B optional in certain cases.
    So @Crissov, you might open a new issue to discuss your proposal. Though given https://wiki.csswg.org/faq#real-physical-lengths, before discussing any syntax proposals, you'd first have provide a solution for how to actually address physical sizes.

    Sebastian

  15. Crissov commented on Dec 10, 2023

    @Crissov
    Contributor

    That is exactly why I did not open another issue. My idea was that if we were to add keywords to the last argument in order to have one of them as an omitable default, we could shoehorn this in as well.

    By the way, I reread the original issue #2513, where much of this thread had already been discussed, and I rediscovered #4440 which I totally had forgotten about. It is related but also quite different.

  16. LeaVerou commented on Jan 31, 2024

    @LeaVerou
    Member

    Strong +1 to defaulting B to 1. It makes sense to require the unit for anything with a unit, and there is no widespread precedent for that in other languages. However, writing round(8.5, 1) is incredibly weird.

    (while we're at it, why are we using commas here? It doesn't match the "we use commas only for repetition" pattern. How about round(A [ by B ]?)?)

  17. tabatkins commented on Jan 31, 2024

    @tabatkins
    Member

    We use commas mostly for repetition, and sometimes for more general argument separation when the grammars would be ambiguous or at least unclear for authors to be adjacent. That latter reason is what applies here (and for all the math functions) - the arguments aren't numbers, they're calculations, and the bounds of a calculation aren't super clear visually on their own.

  18. css-meeting-bot commented on Feb 28, 2024

    @css-meeting-bot
    Member

    The CSS Working Group just discussed [css-values] Let B default to 1 in `round(<strategy>?, A, B)` .

    The full IRC log of that discussion <Frances> Alan: Moving onto the next issue, we are limited by compat concerns.
    <Frances> Tab: Round function takes a rounding strategy, we require the modulus to be rounded. We could omit the modulus.
    <emilio> q+
    <lea> q?
    <astearns> ack emilio
    <lea> q+
    <Frances> Emilio: It feels weird to make lengths invalid and not make it optional, such as rounding a pixel value. Default for the length would be nice.
    <astearns> ack lea
    <Frances> Tab: Rounding to one pixel as a default is not obvious, people might not care about subpixels, but rather the unit they are using.
    <bramus> +1 on what tab just said.
    <emilio> q+
    <astearns> ack emilio
    <Frances> Lea: Should not have to specify 1, whereas dimensions such as length, would make more sense on what you are rounding by, pixels might not be used too much today. Would like default for lengths, possibly not pixels.
    <lea> qq+ to reply to emilio
    <Frances> Emilio: In length there is a single unit that other lengths end up computing to.
    <astearns> ack lea
    <Zakim> lea, you wanted to react to emilio to reply to emilio
    <Frances> Lea: Sensible defaults are great when centered around user needs or could be confusing.
    <oriol> q+
    <Frances> Alan: It is reasonable to have numbers default to 1, but not reasonable for numbers to default to invalid for another type.
    <Frances> Lea: Which invalid results?
    <oriol> q-
    <lea> q?
    <Frances> Alan: Once taking the pattern of omitting the argument and applying it, it could create invalid creations.
    <florian> q+
    <bramus> q+
    <emilio> q+
    <Frances> Lea: When rounding numbers will be clear, when rounding lengths will not be clear.
    <emilio> ack emilio
    <lea> q+
    <astearns> ack Frances
    <astearns> ack florian
    <Frances> Alan: There could be a reasonable default, omit the parameter in some implications but not others.
    <oriol> +1 to Florian
    <Frances> Florian: Figuring out more syntax is necessary, you need to express something. Figuring out which syntax is implied. For rounding things, separate digits after the comma. Does 10 mean 10 digits after the period or before the period? For lengths, have to think about this stuff in what you mean.
    <astearns> ack bramus
    <lea> +1 to florian, that's exactly what I was saying too (but always good to have multiple framings)
    <lea> q?
    <Frances> Bramus: If the default only cares for one of the cases, then it's not a good default?
    <Frances> Alan: It could make it difficult for an author to reason whether to use the parameter or not.
    <astearns> ack lea
    <Frances> Lea: If it's the concern, round could possibly not have a default. It takes a parameter to round by.
    <Frances> Alan: We can likely resolve on making the change.
    <Frances> PROPOSAL: Allow the modulus parameter to be optional in the case of rounding numbers and default to 1
    <Frances> Alan: Objections? No objections.
  19. bramus commented on Feb 28, 2024

    @bramus
    Contributor

    This got cut off from the meeting notes, as the switch to the next topic happened a bit too soon, but we resolved on the PROPOSAL as listed in the meeting notes:

    RESOLVED: Allow the modulus parameter to be optional in the case of rounding numbers and default to 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions