Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Int(Float(Int.max)) give me an error?

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?

like image 300
Sweeper Avatar asked Apr 30 '17 12:04

Sweeper


People also ask

What happens if you assign a float to an int?

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.

How do you check if input is float or integer?

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”.


2 Answers

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 Ints 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.

like image 115
vacawama Avatar answered Sep 27 '22 23:09

vacawama


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.

like image 20
Martin R Avatar answered Sep 27 '22 21:09

Martin R