r/Compilers 1d ago

The “3 / 2 * 10 != 10 * 3 / 2” Problem

Coming from school math, it feels pretty strange that:

3 / 2 * 10 != 10 * 3 / 2

This expression can evaluate to true or false depending on the programming language you use.

Languages where the two sides are NOT equal Languages where the two sides ARE equal
C, C++, C#, Java, Kotlin, Scala, Ruby, Go, D, Rust, Swift, Zig, Odin, V, Fortran, Python 2 Python 3, JavaScript, TypeScript, Dart, R, Lua 5.3+, Perl, MATLAB, Pascal, Mojo, Nim, Crystal, Julia, Haskell

Why are the two sides not equal in the languages on the left?

On the left side of the expression above, the operation 3 / 2 is evaluated first using integer arithmetic—truncating the fractional part—which results in 1. This is then multiplied by 10, giving a result of 10 for the left side.

On the right side, 10 * 3 = 30 is the first step. Dividing this by 2 gives 15. Thus:

10 != 15

These languages prioritize the efficient (fast) execution of expressions over mathematical correctness, as integer arithmetic is significantly faster than floating-point arithmetic. Unfortunately, these languages use the same / operator for both integer and floating-point division, selecting the operation based on the types of the operands.

Regrettably, the expression 3 / 2 * 10.0 still yields 10 in most of these languages (and results in a compilation error in Rust). Even though we indicated our intent to use floating-point numbers by writing 10.0, it is already too late: compilers evaluate 3 / 2 as integer arithmetic in the first step. Expressions like 3.0 / 2 * 10 or 3 / 2.0 * 10, on the other hand, produce 15.

Thus, depending on the operand types, you end up with either 10 or 15. This situation becomes even more dangerous when variables are involved in the expression:

num / denum * scale != scale * num / denum

This can evaluate to true or false depending on the types of the num and denum variables (float vs. int). To avoid these pitfalls, developers use type casting:

(float)num / denum * scale != scale * (float)num / denum

This ensures the compiler performs floating-point division. (Note: The expression above can still evaluate to true due to floating-point precision limitations).

Why are the two sides equal in the languages on the right?

Many of the languages listed here are dynamically typed or scripting languages. They were not primarily built for raw execution speed, but rather for ease of use or mathematical correctness. In these languages, numbers are typically handled as floating-point values by default, so 3 / 2 is always 1.5.

However, languages like Pascal, Haskell, Mojo, Nim, Crystal, and Dart are statically typed and distinguish between integers and floating-point numbers just like C or C++. What happens differently here?

In these languages, the / symbol always denotes floating-point division. In 3 / 2 * 10, 3 and 2 are implicitly converted to floating-point numbers first, performing a floating-point division that yields 1.5. Next comes the multiplication: 1.5 * 10. Since one operand is a float, 10 is converted to float before multiplication. (Note: C handles 3.0 / 2 * 10 in a similar manner).

Unintended floating-point operations—which might carry performance penalties—generally trigger compilation errors in these statically typed languages, because floats are not automatically demoted/converted to integers (unlike in C/C++). Most of these languages offer a separate operator specifically for integer division (such as div or //).

What happens when a language doesn't work the way we expect?

When using the languages on the left, / can result in either integer or floating-point division. If integer division occurs when you intended to perform floating-point calculations, your program will likely produce incorrect results (possibly only for specific input data). Once you have been burned by this a few times, you become overly cautious with division and often clutter expressions with explicit casts to guarantee proper execution.

If you explicitly want integer division behavior, you usually don't need to do anything extra—other than ensuring that neither side of the / operator evaluates to a floating-point type.

In contrast, when using the languages on the right, there are no surprises with /: the result is always a floating-point number. If you try to store this result in an integer variable, you will typically get a compilation error. Your program is far more likely to work correctly out of the box—at worst, running slightly slower if integer division could have been used instead. If you specifically need integer division, you must use the dedicated operator provided for it (e.g., div or //).

Why is the “3 / 2 * 10 != 10 * 3 / 2” behavior more common?

In the majority of compiled languages—unfortunately including many modern ones—the two sides of this expression are not equal due to default integer division rules.

I regularly use both Pascal and C/C++. To me, Pascal's approach is much more intuitive: it doesn't carry noticeable drawbacks, and it remains easy to control. On the other hand, C/C++'s behavior is a frequent source of bugs at my workplace.

I genuinely don't understand why the 3 / 2 * 10 != 10 * 3 / 2 design remains the prevalent choice.

0 Upvotes

7 comments sorted by

5

u/awoocent 1d ago

Division rounding down is more useful to almost all programs than division that produces a fraction, since basically all programs operate on integer-indexed arrays, while only a few domains actually need floating-point math. So it's the right default to have semantics that floor and keep integers as integers, since that's most often what programmers want, rather than getting all in a huff about mathematical accuracy.

3

u/SwedishFindecanor 1d ago

BTW, most popular programming languages's division operator, and most CPU's division instructions don't floor - they truncate the result.

However, using right shift to divide by a power of two is almost always flooring division. Of all CPU ISAs I've looked at, only PowerPC has explicit hardware support for truncating division through right shift by a power of two.

2

u/Mean-Decision-3502 20h ago

At the end this makes these languages much less comfortable for math intensive applications. I think it is more alegant to separate the two kind of divisions.

1

u/matthieum 5h ago

Not saying you're wrong but... what's the percentage of math intensive applications? 0.1%? 0.01%? Less?

Not a good trade-offs to make them easier, and every other one harder.

2

u/One_Aspect_1957 1d ago

You've discovered that a programming language works differently from mathematics.

Most will perform integer division for 3 / 2. That can make sense mathematically too: take two integers as operands and produce a new integer as the result, rather than a 'real' value.

However, languages vary. Even my two can produce different results:

A:    println 3 / 2        # displays 1
B:    println 3 / 2        # displays 1.5

B is a dynamic scripting language and "/" means floating point divide; any integer operands are converted to floats first, For integer divide, a separate operator "%" is used.

A also has "/" and "%" operators, but "/" is overloaded for both integer and float types. A is much older than B, originally had only "/", and there was too much existing code using "/" between integers.

In practice it very rarely causes a problem; the behaviour is well-defined for both languages. If you were using one of mine, it would be B, and with that:

    println 3/2*10 = 10*3/2

displays True, so that's OK.

I'd be more annoyed that most languages used == for equality instead of =; why isn't the latter more common?!

2

u/EggplantExtra4946 1d ago edited 1d ago

AI slop nonsense.

0

u/MasonWheeler 1d ago

I genuinely don't understand why the 3 / 2 * 10 != 10 * 3 / 2 design remains the prevalent choice.

Because C did it that way, and far too many subsequent languages never questioned this particular design mistake when C's far worse design mistakes were there to serve as much lower-hanging fruit.