Comparing number greater than Int32.max gives false result

Trying to do some Advent of Code in the christmas holidays, I found a large number and thought I would measure it to see if it was too big to solve using my current naive solution. But what Swift (5.5.2, Xcode 13.2.1) reported was not what I expected:

Welcome to Swift version 5.5.2-dev.
Type :help for assistance.
  1> 2758514936282235 > Int32.max
$R0: Bool = false
  2> 2758514936282235 < Int32.max
error: repl.swift:2:1: error: integer literal '2758514936282235' overflows when stored into 'Int32'
2758514936282235 < Int32.max
^

I don't know if the error on 2758514936282235 < Int32.max makes sense or not, but the fact that the same error does not occur for 2758514936282235 > Int32.max seems unexpected, and what's even more concerning is that according to Swift, 2 758 514 936 282 235 is less than 2 147 483 647 (Int32.max).

Could this comparison behaviour be related to the one I posted about regarding Decimal last summer? Why is Decimal.ulp greater than any decimal value itself?

1 Like

Integer literals are untyped in Swift. They can become any type which is ExpressibleByIntegerLiteral. The compiler will infer the intended type from context if possible, and default to Int otherwise.

In this case, the compiler prefers to have the same type on both sides of the comparison operator, so the literal is inferred to be an Int32.

I will rewrite the examples with Int8 so that we can use smaller numbers to observe the same effect:

128 < Int8.max    // overflow error
128 > Int8.max    // false

The overflow error on the first line occurs because 128 does not fit in the Int8 type.

So why doesn’t the second line also have an overflow error? That’s because the compiler is taking a shortcut. It knows that no value of Int8 can ever be greater than Int8.max, so it simply ignores the left-hand side and returns false.

That is, of course, the wrong answer. It *should* crash, and I would consider the fact that it doesn’t crash to be a compiler bug.

• • •

You can get the correct results without crashing if you use a type large enough to hold the value of the literal. In this case Int works:

(128 as Int) < Int8.max    // false
(128 as Int) > Int8.max    // true

Int also works for your example.

3 Likes

Well, only if you are on a 64-bit platform. It crashes on my Raspberry Pi.

1 Like

This is clearly unacceptable and straight-up incorrect, whatever the type inference. I’m surprised as I haven’t seen this before and I’ve seen a bunch of weird results due to the heterogeneous comparison operators. @idrougge, can you file a bug?

I wonder if @hborla can speak to the operator resolution side of things here. @scanon, who’s maintaining the integer stuff in the standard library these days and how do we…make this not happen?

3 Likes

One (simple) option to resolve this is to make this a compilation error:

  1. left hand side is inferred to be of Int8 type
  2. 128 is unrepresentable as Int8 value
  3. Compilation Error: Integer literal '128' overflows when used as 'Int8' value

same error we'll have if write:

let x: Int8 = 128 // Integer literal '128' overflows when stored into 'Int8'

Sounds like this isn't exactly an integer bug and more likely a literal issue, since it works with explicit types. I'll sync up with @John_McCall (who wrote the current integer literal handling, IIRC) after the holiday break and see what we can do about it, if no one has had a chance to dig into it before then. And yes, a bug report would be much appreciated (please post a link to it here for reference).

4 Likes

Just to confirm, Swift compiler* also exhibits this behaviour. So it's likely not a REPL problem.

* swift-driver version: 1.26.21 Apple Swift version 5.5.2 (swiftlang-1300.0.47.5 clang-1300.0.29.30) Target: x86_64-apple-macosx12.0

1 Like

I haven't done the investigation, but at a guess:

  • The optimizer is folding the tautological comparison.
  • The Int8 value on the left-hand side is now unused.
  • The optimizer is eliminating the now-dead creation of an Int8 value from a literal, even though it contains a potential overflow trap.

I believe the Swift high-level optimizer is generally authorized to eliminate overflow checks if it can prove that the result of the arithmetic is unused, because otherwise we'd be extremely restricted in terms of moving arithmetic around. Obviously the result of that is really bad here; the right fix is probably that uses eliminated via tautology should still "count" somehow in terms of forcing the operands to be normally evaluated.

5 Likes
2 Likes