Very good point.
In xpath 1 (and xslt 1.0), //val[not(. < //val)]
is inherently casting the text values to numbers, and doing the comparison on numbers.
Hence, you get the correct min and max values.
However, in xpath 2 (and xslt 2.0), the text isn't cast to numeric, and instead, a string comparison is done. So using string comparison, '7' is > '12' and hence 7 will still be the highest value (it isn't returning just the last value in the list - try swapping the order of values around)
So what you should do to be safe in both xslt 1 and 2 is to use the number() function to do the cast on the text, to ensure that a numeric comparison is done.
Max:
//val[not(number(.) < //val)]
Min:
//val[not(number(.) > //val)]
Using Saxon with the stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<x>
<Max1>
<xsl:value-of select="//val[not(number(.) < //val)]" />
</Max1>
<Min1>
<xsl:value-of select="//val[not(number(.) > //val)]" />
</Min1>
<Max2>
<xsl:value-of select="/document/val[. = max(//val/text())]" />
</Max2>
<Min2>
<xsl:value-of select="/document/val[. = min(//val/text())]" />
</Min2>
</x>
</xsl:template>
</xsl:stylesheet>
Returns
<x>
<Max1>12</Max1>
<Min1>0</Min1>
<Max2>12</Max2>
<Min2>0</Min2>
</x>
as expected.