You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[css-values] Let B default to 1 in round(<strategy>?, A, B) #9668
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).
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.
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?
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".
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.
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.
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.
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:
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.
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.
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.
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".
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.
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.
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.
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 ]?)?)
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.
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.
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.
Activity
SebastianZ commentedon Dec 4, 2023
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 toround(2.3em, 1em).Sebastian
Loirooriol commentedon Dec 4, 2023
No, that would be bad. Let's say
1em = 16px, thenround(2.3em)should produce the same asround(36.8px). Butround(2.3em, 1em)is2em = 32px, whileround(36.8px, 1px)is37px. Making it unit-dependent is also not clear forround(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.tabatkins commentedon Dec 4, 2023
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.Yes, we could do this. Is there a particular reason to do so, tho?
Loirooriol commentedon Dec 4, 2023
It just seems strange to require the
1when 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".SebastianZ commentedon Dec 6, 2023
Converting the unit before rounding is totally unexpected from an author's standpoint.
The point was for rounding when only having a single unit. So,
round(1em + 10px)would still be invalid. Onlyround(2.3em),round(42.8deg), orround(87.39%)would be valid (being equivalent toround(2.3em, 1em),round(42.8deg, 1deg), andround(87.39%, 1%), respectively).Allowing to omit B for numbers but not dimensions or percentages may cause confusion.
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
jfkthame commentedon Dec 6, 2023
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 haveround(--var(foo) + --var(bar)), wherefooandbarare both lengths? Whether theroundworks (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.tabatkins commentedon Dec 6, 2023
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
1for B, I'm fine with that. Agenda+ to discuss it.Crissov commentedon Dec 7, 2023
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 like2.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.
emor%, expressed as an integer amount in an absolute unit likepx.My educated guess is that the most common use case by far will be rounding (calculated) lengths to whole pixels. Since
pxis 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 to4.3instead of4and likewiseround(4.32em, 1)=4.3em, whileround(4.32em, 1em)would yield4em. The default value would be0.Finally, instead of defaulting to
1,1<resulting unit>or1<canonical unit>, the second parameter could support keywords:round(4.32em, auto)=4emround(4.32em, default)= e.g.69pxround(4.32em, none)= e.g.69.12pxtabatkins commentedon Dec 8, 2023
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.
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.
These either continue to have the existing problems of a default rounding precision, or are equivalent to just providing a trivial value.
SebastianZ commentedon Dec 8, 2023
That's true in any case, as
--foocan 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, andround(10px + 1em)=round(10px + 1em, 1px)=26px(in case1em=16px).That makes it explainable and less fragile.
The precision is not unpredictable, it is unit-based.
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.
Note that Firefox already ships the
round()function. So, a change in behavior will break any current usages.Sebastian
Loirooriol commentedon Dec 8, 2023
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.
tabatkins commentedon Dec 8, 2023
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".Crissov commentedon Dec 9, 2023
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 #614Either
round(100mm, physical)orround(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.
SebastianZ commentedon Dec 9, 2023
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
Crissov commentedon Dec 10, 2023
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.
LeaVerou commentedon Jan 31, 2024
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 ]?)?)tabatkins commentedon Jan 31, 2024
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.
css-meeting-bot commentedon Feb 28, 2024
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.
bramus commentedon Feb 28, 2024
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:
[css-values-4] Allow B in round() to be omitted, default to 1. #9668
[css-values-4] Forgot to change the production rule for round(). #9668