In most programming languages that have an int
type, the most significant bits of the product of
multiplication are thrown away
without warning, which can obviously lead to errors. This can not be blamed on the hardware,
because most CPUs that provide multiplication can produce results with twice the number of bits as the input.
So, on an x64, or ARM64, or some RISC-V, a 64-bit multiply yields a 128-bit product.
One way of expressing this in a programming language would be with a new ternary operator: the fused multiply-divide, also known as the scaling operator. It takes three operands:
multiplicand × multiplier ÷ divisor
It makes use of a double wide dividend to avoid overflow errors. On Intel processors, you use the imul
and idiv
instructions together.
mov a, multiplicand imul multiplier ; multiply a and the multiplier, putting the product in d:a. idiv divisor ; divide d:a by the divisor, putting the quotient in a. jo overflow ; take action if the quotient was too big for a.
The idiv
instruction takes a double wide dividend. The overflow test is to guard against the possibility of the
quotient not fitting nicely into the result type, which could occur if the multiplicand and multiplier are large and the
divisor is small. Unfortunately, there is a design error in the Intel architecture: the state of OF
,
the overflow flag, is left undefined by the idiv
instruction, so it faults instead.
The situation is worse on ARM and RISC-V processors because they fail to provide a divide instruction that takes a double-wide dividend. I think the reason for this is that CPU designers do not like division. It is complex, it takes up a lot of real estate, and it is slow. Having slow instructions in the instruction set can complicate timing. These used to be valid excuses, but not anymore. Moore's Law is still in effect, so we should have complete division instructions.
On cheap int
machines, the scaling operator makes a great substitute for floating point. For example, to multiply by
π, just
multiplicand × 355 ÷ 113
which is very close to multiplying by 3.141593.
It is also ideal for implementing multiplication and division for scaled numbers.
multiplicand × multiplier ÷ scale_factor dividend × scale_factor ÷ divisor