Douglas Crockford

Blog

Books

Videos

2024 Appearances

JavaScript

Misty

JSLint

JSON

Github

Electric Communities

Mastodon/Layer8

Flickr Photo Album

ResearchGate

LinkedIn

Pronouns: pe/per

About

Scaling Operator

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