Every atomic type has a constructor function of the same name, so xs:decimal("39.50") equals "39.50" cast as xs:decimal, and castable as asks whether a cast would succeed. Casts from strings follow XML Schema's lexical rules, which are stricter than most languages:
xp 'xs:decimal("39.50")' 'xs:integer("007")' 'xs:integer("12.0")' \
'"12.0" castable as xs:integer' 'xs:boolean("yes")' 'number("n/a")' \
'xs:date("2026-10-01T08:00:00Z")' 'xs:date(xs:dateTime(//sent))' 'xs:numeric(true())'xs:decimal("39.50") => 39.5
xs:integer("007") => 7
xs:integer("12.0") => error FORG0001
"12.0" castable as xs:integer => false()
xs:boolean("yes") => error FORG0001
number("n/a") => NaN
xs:date("2026-10-01T08:00:00Z") => error FORG0001
xs:date(xs:dateTime(//sent)) => xs:date("2026-10-01Z")
xs:numeric(true()) => 1.0e012.0 is not a lexical integer, Booleans accept only true, false, 1 and 0, and a date string must not carry a time; casting an xs:dateTime to a date, by contrast, works and keeps the time zone. number() is XPath 1.0's lenient conversion, returning NaN instead of failing, which hides bad data: in a pipeline, guard a constructor with castable as and quarantine what fails. And xs:numeric, a union type, constructs an xs:double (1.0e0), not the 1 older references show.