I observed something really strange. If you run this code in Swift:
Int(Float(Int.max))
It crashes with the error message:
fatal error: Float value cannot be converted to Int because the result would be greater than Int.max
This is really counter-intuitive, so I expanded the expression into 3 lines and tried to see what happens in each step in a playground:
let a = Int.max
let b = Float(a)
let c = Int(b)
It crashes with the same message. This time, I see that a
is 9223372036854775807 and b
is 9.223372e+18. It is obvious that a
is greater than b
by 36854775807. I also understand that floating points are inaccurate, so I expected something less than Int.max
, with the last few digits being 0.
I also tried this with Double
, it crashes too.
Then I thought, maybe this is just how floating point numbers behave, so I tested the same thing in Java:
long a = Long.MAX_VALUE;
float b = (float)a;
long c = (long)b;
System.out.println(c);
It prints the expected 9223372036854775807!
What is wrong with swift?
You can safely assign a floating point variable to an integer variable, the compiler will just truncate (not round) the value. At most the compiler might give you a warning, especially if assigning from e.g. a double or bigger type.
Initialize a variable, say X, to store the integer value of N. Convert the value float value of N to integer and store it in X. Finally, check if (N – X) > 0 or not. If found to be true, then print “NO”.
There aren't enough bits in the mantissa of a Double
or Float
to accurately represent 19
significant digits, so you are getting a rounded result.
If you print the Float
using String(format:)
you can see a more accurate representation of the value of the Float
:
let a = Int.max
print(a) // 9223372036854775807
let b = Float(a)
print(String(format: "%.1f", b)) // 9223372036854775808.0
So the value represented by the Float
is 1
larger than Int.max
.
Many values will be converted to the same Float
value. The question becomes, how much would you have to reduce Int.max
before it results in a different Double
or Float
value.
Starting with Double
:
var y = Int.max
while Double(y) == Double(Int.max) {
y -= 1
}
print(Int.max - y) // 512
So with Double
, the last 512
Int
s all convert to the same Double
.
Float
has fewer bits to represent the value, so there are more values that all map to the same Float
. Switching to - 1000
so that it runs in reasonable time:
var y = Int.max
while Float(y) == Float(Int.max) {
y -= 1000
}
print(Int.max - y) // 274877907000
So, your expectation that a Float
could accurately represent a specific Int
was misplaced.
Follow up question from the comments:
If float does not have enough bits to represent Int.max, how is it able to represent a number one larger than that?
Floating point numbers are represented as two parts: mantissa and exponent. The mantissa represents the significant digits (in binary) and the exponent represents the power of 2. As a result, a floating point number can accurately express an even power of 2 by having a mantissa of 1 with an exponent that represents the power.
Numbers that are not even powers of 2 may have a binary pattern that contains more digits than can be represented in the mantissa. This is the case for Int.max
(which is 2^63 - 1) because in binary that is 111111111111111111111111111111111111111111111111111111111111111
(63 1's). A Float
which is 32 bits cannot store a mantissa which is 63 bits, so it has to be rounded or truncated. In the case of Int.max
, rounding up by 1 results in the value
1000000000000000000000000000000000000000000000000000000000000000
. Starting from the left, there is only 1 significant bit to be represented by the mantissa (the trailing 0
's come for free), so this number is a mantissa of 1
and an exponent of 64
.
See @MartinR's answer for an explanation of what Java is doing.
Swift and Java behave differently when converting a "too large" floating point
number to an integer. Java truncates any floating point value
larger than Long.MAX_VALUE = 2^63-1
:
long c = (long)(1.0E+30f);
System.out.println(c);
// 9223372036854775807
Swift expects that the value is in the range of Int
, and aborts
with a runtime exception otherwise:
/// Creates a new instance by rounding the given floating-point value toward
/// zero.
///
/// - Parameter other: A floating-point value. When `other` is rounded toward
/// zero, the result must be within the range `Int.min...Int.max`.
public init(_ value: Float)
Example:
let c = Int(Float(1.0E30))
print(c)
// fatal error: Float value cannot be converted to Int because the result would be greater than Int.max
The same happens with your value Float(Int.max)
, which is the
floating point representable value closest to Int.max
and happens
to be larger than Int.max
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With