When you write:
print("\(floatValue)")
or
print(floatValue)
It will end up using floatValue.description
which is the shortest textual decimal representation of the value that will convert back to the exact same floating point value (see LosslessStringConvertible
).
So let's say we have these:
let v1: Float = 492612.6
let v0 = v1.nextDown // The closest representable value less than v1
let v2 = v1.nextUp // The closest representable value greater than v1
Note that the value of v1
is not exactly 492612.6, it's just the closest representable floating point value of that value. Here's what their bit patterns (sign bit, exponent bits and significand bits) look like:
v0.bitPattern == 0b0_10010001_11100001000100010010010
v1.bitPattern == 0b0_10010001_11100001000100010010011
v2.bitPattern == 0b0_10010001_11100001000100010010100
When we print
v0
, v1
, v2
(which ends up using their .description
property), we get the shortest textual decimal representation that can be used to convert back to the exact same underlaying floating point values:
print(v0) // 492612.56
print(v1) // 492612.6
print(v2) // 492612.62
print(v0 == Float("492612.56")) // true
print(v1 == Float("492612.6")) // true
print(v2 == Float("492612.62")) // true
Again, note that the decimal representations we see here are just the shortest ones that will map back to the same underlying floating point values, and they are not necessarily exact:
print(v1 == Float("492612.59")) // true
print(492612.6 as Float == 492612.59 as Float) // true
Since every (finite) single precision IEEE 754 floating point value can be represented exactly with a finite number of decimal digits, we can see what they are:
print(String(format: "%.10f", v0)) // 492612.5625000000
print(String(format: "%.10f", v1)) // 492612.5937500000
print(String(format: "%.10f", v2)) // 492612.6250000000
So, 492612.6 is not representable exactly as a Float
value, and the closest representable Float
value is exactly 492612.59375.
Note that there would be little point in writing this Float
value in decimal as
492612.59 or
492612.594 or
492612.5938 instead of just
492612.6
since they all map to the same closest representable Float
value, because the distance between representable values at this magnitude is
0.03125 == Float(492612.6).ulp
.
And here's what v1 = Float(492612.6)
looks like when printed with 11 to 1 significant decimal digits using .localizedStringWithFormat
on my system:
for p in (1 ... 11).reversed() {
print(String.localizedStringWithFormat("%.\(p)g", v1))
}
492 612,59375
492 612,5938
492 612,594
492 612,59
492 612,6
492 613
4,9261Ă—10^+05
4,926Ă—10^+05
4,93Ă—10^+05
4,9Ă—10^+05
5Ă—10^+05
(Format specifiers (like "%f"
, "%g"
and "%.3g"
) are described in the IEEE printf specification.)