为什么将整数字符串转换为 float 和 double 会产生不同的结果?

Why does converting an integer string to float and double produce different results?

let n = "77777777"
let i = Int(n)!
let f = Float(n)!
let d = Double(n)!

print(i)
print(f)
print(d)
print(Int(f))
print(Int(d))

产生:

77777777

7.777778e+07

77777777.0

77777776

77777777

为什么?使用 Xcode 11, Swift 5. 看起来很相似,但它以十进制数开头,而不是整数。此外,这里确实没有进行浮点运算。

好的,请看https://www.h-schmidt.net/FloatConverter/IEEE754.html处的浮点转换器。当您以二进制和十六进制表示形式输入数字时,它会向您显示存储的位,并且还会为您提供转换引起的错误。问题在于数字在标准中的表示方式。在浮点数中,错误确实是-1。

实际上,7777777277777780 范围内的任何数字都会为您提供 77777776 作为尾数的内部表示。

数字存储在有限的内存中。无论您是否进行浮点运算,都需要一种在二进制内存中对十进制数进行编码的方法。只要您的内存有限(即在现实世界中总是如此),您就必须选择将您的位用于高范围或高精度,或者两者之间的权衡。

超过 7 位数会让您进入 Float 的第一个权衡“区域”。您可以“做到”,但需要权衡:在如此高的幅度下,您会失去一些精度。在这种情况下,整数会四舍五入到最接近的 10。

Float 是单精度 IEEE 754 浮点指针数。它的第一个“权衡”区域位于 16,777,217。从 016,777,216,每个整数都可以精确表示。在那之后,没有足够的精度来指定低至 2^0(个,a.k.a。单位)的数字。下一个最好的事情是将它正确地表示为最接近的 2^1 (双)。

看看这个:

import Foundation

for originalInt in 16_777_210 ... 16_777_227 {
    let interMediateFloat = Float(originalInt)
    let backAsInt = Int(interMediateFloat)
    print("\(originalInt) -> \(backAsInt)")
}

print("\n...\n")

for originalInt in 33_554_430 ... 33_554_443 {
    let interMediateFloat = Float(originalInt)
    let backAsInt = Int(interMediateFloat)
    print("\(originalInt) -> \(backAsInt)")
}

打印:

16777210 -> 16777210
16777211 -> 16777211
16777212 -> 16777212
16777213 -> 16777213
16777214 -> 16777214
16777215 -> 16777215
16777216 -> 16777216 // Last precisely representable whole number
16777217 -> 16777216 // rounds down
16777218 -> 16777218 // starts skipping by 2s
16777219 -> 16777220
16777220 -> 16777220
16777221 -> 16777220
16777222 -> 16777222
16777223 -> 16777224
16777224 -> 16777224
16777225 -> 16777224
16777226 -> 16777226
16777227 -> 16777228

...

33554430 -> 33554430
33554431 -> 33554432
33554432 -> 33554432
33554433 -> 33554432
33554434 -> 33554432 // Last whole number representable to the closest "two"
33554435 -> 33554436 // rounds up
33554436 -> 33554436
33554437 -> 33554436 // starts skipping by 4s
33554438 -> 33554440
33554439 -> 33554440
33554440 -> 33554440
33554441 -> 33554440
33554442 -> 33554440
33554443 -> 33554444

等等。随着幅度变大,整数的表示精度越来越低。在极端情况下,最大整数值(340,282,346,638,528,859,811,704,183,484,516,925,440)和第二大整数值(340,282,326,356,119,256,160,033,759,537,265,639,424)相差20,282,409,603,651,670,423,947,251,286,0162^104)。

表达如此大的数字的能力恰恰是以无法精确存储该数量级附近的许多数字为代价的。舍入发生。或者,像 Swift 的 Int 这样的二进制整数对整数具有完美的精度(总是存储到正确的 ones/units),但为此付出的最大尺寸要小得多(仅 2,147,483,647 签名 Int32).