w3c / fxtf-drafts

Mirror of https://hg.fxtf.org/drafts
https://drafts.fxtf.org/
Other
68 stars 49 forks source link

[geometry-1] Is a specific handling for Infinity implied by "NaN-safe minimum/maximum"? #556

Open towerofnix opened 4 months ago

towerofnix commented 4 months ago

Document conventions includes the following description for NaN-safe minimum and NaN-safe maximum:

The NaN-safe minimum of a non-empty list of unrestricted double values is NaN if any member of the list is NaN, or the minimum of the list otherwise.

Analogously, the NaN-safe maximum of a non-empty list of unrestricted double values is NaN if any member of the list is NaN, or the maximum of the list otherwise.

This was introduced in #395 following discussion in #222 and mailing lists (quoted in the latter issue).

Although Infinity is explicitly mentioned in the (quoted) mailing list, it isn't in the spec or issue discussion.

On Sat, Oct 15, 2016 at 2:47 PM, Simon Fraser smfr@me.com wrote: (excerpt) Using unrestricted doubles forces implementors to handle NaN and Inf values for x, y, width and height, and to correctly propagate NaN and Inf through steps used to calculate left, top, right, bottom (assuming IEEE rules, though the spec does not make this explicit). […] Is the complexity worth it?

On Oct 16, 2016, at 9:34 AM, Rik Cabanier cabanier@gmail.com wrote: (excerpt, emphasis mine) yes. We allowed this so NaN and Inf would signal when matrix calculations hit edge conditions. Instead of throwing or giving inaccurate result, it was decided to allow the values so authors can check for those. The alternative would be to throw and we feared that this would break a lot of code since people don't test with exceptions. […]

So I just wanted to ask, is the behavior for Infinity currently defined? At the moment we're using the verb "if ... is NaN" to specify the special behavior ("the NaN-safe minimum … is NaN"), and the default behavior is otherwise only "the minimum of" the list of terms. I don't see definitions or references for what "is NaN" and "the minimum of" mean within this spec, which seems like potentially undefined behavior to me.

Test script to observe behavior of infinity on various edges and areas: ```js rects = { infinityTowardsRight: new DOMRect(0, 0, Infinity, 0), infinityTowardsLeft: new DOMRect(0, 0, -Infinity, 0), infinityAtRight: new DOMRect(Infinity, 0, 0, 0), infinityAtLeft: new DOMRect(-Infinity, 0, 0, 0), infinityLeftToRight: new DOMRect(-Infinity, 0, Infinity, 0), infinityRightToLeft: new DOMRect(Infinity, 0, -Infinity, 0), infinityTowardsBottom: new DOMRect(0, 0, 0, Infinity), infinityTowardsTop: new DOMRect(0, 0, 0, -Infinity), infinityAtBottom: new DOMRect(0, Infinity, 0, 0), infinityAtTop: new DOMRect(0, -Infinity, 0, 0), infinityTopToBottom: new DOMRect(0, -Infinity, 0, Infinity), infinityBottomToTop: new DOMRect(0, Infinity, 0, -Infinity), bottomRightQuadrant: new DOMRect(0, 0, Infinity, Infinity), bottomLeftQuadrant: new DOMRect(0, 0, -Infinity, Infinity), topRightQuadrant: new DOMRect(0, 0, Infinity, -Infinity), topLeftQuadrant: new DOMRect(0, 0, -Infinity, Infinity), planeTopBottomLeftRight: new DOMRect(-Infinity, -Infinity, Infinity, Infinity), planeTopBottomRightLeft: new DOMRect(Infinity, -Infinity, -Infinity, Infinity), planeBottomTopLeftRight: new DOMRect(Infinity, -Infinity, Infinity, -Infinity), planeBottomTopRightLeft: new DOMRect(Infinity, Infinity, -Infinity, -Infinity), }; props = ['x', 'y', 'width', 'height', 'left', 'right', 'top', 'bottom']; console.log(`Rectangle,` + props.join(',')); for (const [name, rect] of Object.entries(rects)) { console.log(`${name},${props.map(p => rect[p]).join(',')}`); } ```
Results are conveniently identical in Safari 17.4, Firefox 123, Chrome 122: | Rectangle | x | y | width | height | left | right | top | bottom | |-------------------------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|-----------| | infinityTowardsRight | 0 | 0 | Infinity | 0 | 0 | Infinity | 0 | 0 | | infinityTowardsLeft | 0 | 0 | -Infinity | 0 | -Infinity | 0 | 0 | 0 | | infinityAtRight | Infinity | 0 | 0 | 0 | Infinity | Infinity | 0 | 0 | | infinityAtLeft | -Infinity | 0 | 0 | 0 | -Infinity | -Infinity | 0 | 0 | | infinityLeftToRight | -Infinity | 0 | Infinity | 0 | NaN | NaN | 0 | 0 | | infinityRightToLeft | Infinity | 0 | -Infinity | 0 | NaN | NaN | 0 | 0 | | infinityTowardsBottom | 0 | 0 | 0 | Infinity | 0 | 0 | 0 | Infinity | | infinityTowardsTop | 0 | 0 | 0 | -Infinity | 0 | 0 | -Infinity | 0 | | infinityAtBottom | 0 | Infinity | 0 | 0 | 0 | 0 | Infinity | Infinity | | infinityAtTop | 0 | -Infinity | 0 | 0 | 0 | 0 | -Infinity | -Infinity | | infinityTopToBottom | 0 | -Infinity | 0 | Infinity | 0 | 0 | NaN | NaN | | infinityBottomToTop | 0 | Infinity | 0 | -Infinity | 0 | 0 | NaN | NaN | | bottomRightQuadrant | 0 | 0 | Infinity | Infinity | 0 | Infinity | 0 | Infinity | | bottomLeftQuadrant | 0 | 0 | -Infinity | Infinity | -Infinity | 0 | 0 | Infinity | | topRightQuadrant | 0 | 0 | Infinity | -Infinity | 0 | Infinity | -Infinity | 0 | | topLeftQuadrant | 0 | 0 | -Infinity | Infinity | -Infinity | 0 | 0 | Infinity | | planeTopBottomLeftRight | -Infinity | -Infinity | Infinity | Infinity | NaN | NaN | NaN | NaN | | planeTopBottomRightLeft | Infinity | -Infinity | -Infinity | Infinity | NaN | NaN | NaN | NaN | | planeBottomTopLeftRight | Infinity | -Infinity | Infinity | -Infinity | Infinity | Infinity | -Infinity | -Infinity | | planeBottomTopRightLeft | Infinity | Infinity | -Infinity | -Infinity | NaN | NaN | NaN | NaN |

The consistent behavior is obviously a good sign, but I'd like to know if the spec is already explicit enough and browsers are strictly following it, or if they've just settled on identical behavior here by happenstance.

Also relevant are the definitions for top/left/right/bottom (bottom of 3. The DOMRect interfaces):

The top attribute, on getting, must return the NaN-safe minimum of the y coordinate and the sum of the y coordinate and the height dimension.

The right attribute, on getting, must return the NaN-safe maximum of the x coordinate and the sum of the x coordinate and the width dimension.

The bottom attribute, on getting, must return the NaN-safe maximum of the y coordinate and the sum of the y coordinate and the height dimension.

The left attribute, on getting, must return the NaN-safe minimum of the x coordinate and the sum of the x coordinate and the width dimension.

Note use of "the sum of".

x, y, width, height are all unrestricted doubles, which "is a floating point numeric type that corresponds to the set of all possible double-precision 64-bit IEEE 754 floating point numbers, finite, non-finite, and special "not a number" values (NaNs)".

I'm not acquainted with IEEE 754 to know if it specifically defines computations for minimum/maximum (which would include details for Infinity, e.g. "the minimum of any set of numbers, not including NaN, and including negative infinity, is negative infinity"), but given the previous concern over NaN propagation being addressed within this spec (rather than externally or implicitly), I'm wondering if Infinity should receive similar treatment.

I also don't know if "is NaN" is explicit enough as-is to imply "is the exact value NaN" (i.e. synonymous with JavaScript Number.isNaN) - I understand it not to imply "infinity is NaN", but this isn't explicit in the spec.

towerofnix commented 4 months ago

This was kind of an obvious thing for me to overlook initially, but -Infinity + Infinity and Infinity + -Infinity are both NaN, which makes corresponding edges always be NaN: you're getting the minimum/maximum of ±Infinity and NaN, which is always NaN, by spec.

planeBottomTopLeftRight is the only remaining "surprising" case (new DOMRect(Infinity, -Infinity, Infinity, -Infinity)), but here we're summing like signs, (x: Infinity) + (width: Infinity) and (y: -Infinity) + (height: -Infinity), so no NaN is involved and normal minimum/maximum logic applies. This is also, uh, an error in my chart, since really this just means "the top-right quadrant relative to (Infinity, -Infinity)" — the correct plane definition here would be new DOMRect(-Infinity, Infinity, Infinity, -Infinity).

I don't believe it's possible to represent a plane with DOMRect - on a practical level because those involve opposing infinities (so bounds always perform min/max with NaN), and on a theoretical level because planes don't fundamentally express an origin, they're just the dimension space. DOMRect is always in terms of an origin point (a DOMRect could be described as "the positive or negative width/height extending away from an origin x/y").