If one argument of a binary operator is unknown, then assume it is the same type as
the other argument.

Reverse the arguments, and look for an exact match
with an operator which points to itself as being
commutative. If found, then reverse the arguments in the
parse tree and use this operator.

Look for the best match.

Make a list of all operators of the same name.

If only one operator is in the list, use it if the
input type can be coerced, and throw an error if the type
cannot be coerced.

Keep all operators with the most explicit matches for
types. Keep all if there are no explicit matches and move
to the next step. If only one candidate remains, use it
if the type can be coerced.

If any input arguments are "unknown", categorize the
input candidates as boolean, numeric, string, geometric,
or user-defined. If there is a mix of categories, or more
than one user-defined type, throw an error because the
correct choice cannot be deduced without more clues. If
only one category is present, then assign the "preferred
type" to the input column which had been previously
"unknown".

Choose the candidate with the most exact type matches,
and which matches the "preferred type" for each column
category from the previous step. If there is still more
than one candidate, or if there are none, then throw an
error.

There is only one exponentiation operator defined in the
catalog, and it takes float8 arguments.
The scanner assigns an initial type of int4 to both arguments of this query expression:

tgl=> select 2 ^ 3 AS "Exp";
Exp
---
8
(1 row)

So the parser does a type conversion on both operands and the
query is equivalent to

tgl=> select float8(2) ^ float8(3) AS "Exp";
Exp
---
8
(1 row)

or

tgl=> select 2.0 ^ 3.0 AS "Exp";
Exp
---
8
(1 row)

Note: This last form has the least overhead, since no
functions are called to do implicit type conversion. This is not
an issue for small queries, but may have an impact on the
performance of queries involving large tables.

In this case there is no initial hint for which type to use,
since no types are specified in the query. So, the parser looks for
all candidate operators and finds that all arguments for all the
candidates are string types. It chooses the "preferred type" for
strings, text, for this query.

Note: If a user defines a new type and defines an
operator “||” to work with it, then this query would no longer
succeed as written. The parser would now have candidate types
from two categories, and could not decide which to use.

This example illustrates an interesting result. Traditionally,
the factorial operator is defined for integers only. The
Postgres operator catalog has only
one entry for factorial, taking an integer operand. If given a
non-integer numeric argument, Postgres will try to convert that argument to
an integer for evaluation of the factorial.

tgl=> select (4.3 !);
?column?
--------
24
(1 row)

Note: Of course, this leads to a mathematically suspect
result, since in principle the factorial of a non-integer is not
defined. However, the role of a database is not to teach
mathematics, but to be a tool for data manipulation. If a user
chooses to take the factorial of a floating point number,
Postgres will try to oblige.