Perhaps at the risk of running OT, though I'd argue that the actual topic/issue seems to be about understanding IEEE-754 floating point in general (including conversion to and from decimal strings in Swift, JSON/Javascript, etc.):
I'm curious about what you mean by this. AFAIK:
Any Float value is exactly representable as a Double value, ie Float(Double(floatValue)).bitPattern == floatValue.bitPattern. (I'm using .bitPattern just to handle the fact that nans are not equal to any other nan, including themselves.)
Float(d) == Float(String(d)) for any Double value d where Float(d).isFinite && Float(d) != 0.
Float(doubleValue) is the Float value closest to doubleValue, ie doubleValue is "rounded" towards the closest representable Float value. (I guess I'm wrong here? If so, can you give an example?)
I wish I could find the example that came up in the past, but so far I haven't found it.
Even though someFloat and Double(someFloat) might be the exact same floating point value, there's no guarantee that String(someFloat) and String(Double(someFloat)) are the same string. The strings aren't actual values, but just recipes for constructing values.
The troublesome case is probably a conversion like Float(Double(String(someFloat)). In that case, the recipe intended for a Float is being used to cook up a Double, which might not work out exactly as intended.
It's pretty easy to show what's going on in @cukr's example. The two closest representable Float values are:
7.0385306918512091208591880171403069741059913000... x 10**-26
7.0385313081487913247746609950532486012827332322... x 10**-26
and the two closest representable Double values are:
7.0385309999999990748732225312066332869750876350... x 10**-26
7.0385310000000002228169245060967777876943622661... x 10**-26
The string in question ("7.038531e-26") is just a tiny bit closer to the lower value in Float, so Float("7.038531e-26") returns that value. However it's closer to the upper value in Double, and that Double is closer to the upper Float value than the lower one, so it rounds up when converted to Float. This phenomenon is so common in floating-point arithmetic that it has a name ("double rounding"), and it's why conversions should always be done in a single step when possible. It can happen in almost any chain of conversions A -> B -> C where both steps round (String -> Double -> Float or Double -> Float -> Float16 are the two most common). It can be avoided in most cases by doing the first conversion in a special rounding mode ("round to odd"), which is something we might think about providing eventually.
So this has nothing to do with the bug I mentioned above? Note that the demonstration program in the details (which was originally used to demonstrate SR-12312) reports @cukr's example value as a mismatched conversion.
(And the fix of SR-12312 doesn't seem to be in Swift 5.3 / Xcode 12.1.)
Nowadays it's only the closed Foundation implementation on Apple platforms where the JSON encoding could do better, as demonstrated in this Swift playground example:
.xcplaygroundpage:9:19: error: module 'SwiftFoundation' has no member named 'JSONEncoder'
String(data: try! SwiftFoundation.JSONEncoder().encode(4.18), encoding: .utf8)!
^~~~~~~~~~~~~~~ ~~~~~~~~~~~
But wait, am I still confused or does it actually have to do with SR-12312 after all?
On my machine (without the bug fix (despite having the latest Xcode (see comments in SR-12312))):
$ swiftc --version
Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1)
Target: x86_64-apple-darwin19.6.0 <---
I'll see this:
let someFloat = 7.038531e-26 as Float
print(someFloat == Float(Double(String(someFloat))!))
// Prints false <---
print(someFloat == Float._convert(from: Double(String(someFloat))!).value)
// Prints true <---
(Those should both print true if SR-12312 is fixed, shouldn't they? Ie, there is generics involved, behind the scenes.)
But (and now I'm guessing) on eg @xwu's machine (with the bug fix):
$ swiftc --version
Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1)
Target: x86_64-apple-darwin20.1.0 <---
I think they'll see this:
let someFloat = 7.038531e-26 as Float
print(someFloat == Float(Double(String(someFloat))!))
// Prints true <---
print(someFloat == Float._convert(from: Double(String(someFloat))!).value)
// Prints true
Why do you think the behavior of the concrete initializers would change? Correcting SR-12312 aligns generic conversions to match the behavior of the concrete initializers:
let someFloat = 7.038531e-26 as Float
print(someFloat == Float(Double(String(someFloat))!))
// false
print(someFloat == Float._convert(from: Double(String(someFloat))!).value)
// false
This behavior demonstrates the concept of double rounding exactly as @scanon outlines above.
(I think I mixed up which one of the two was generic ... Thank you both for helping me straighten this out, I'm finally no longer confused, I think. :)
This is a good example of a gripe I have with how entangled the Swift toolchain is with Xcode, etc. on Macs, as discussed on this thread. IMO, it would be better if the Swift toolchain for Macs looked a lot more like the one for Linux (no Xcode/Apple platform development stuff... all that could be an "extension" added on by Xcode).
Foundation is not part of Xcode, it's part of the Apple operating system in use (macOS, iOS, iPadOS, etc.). It gets downloaded and installed when you install macOS, not Xcode. It is fundamental to the Apple system architecture, and has features within it that are needed because it's working on Darwin-based architecture, not Linux, FreeBSD, etc.. For Linux, it's only necessary if you are programming in Swift, it is not foundational for Linux.
If you go to /System/Library/Frameworks and /System/Library/PrivateFrameworks, you will see all the stuff you get when you install MacOS, regardless if you install Xcode at all. If you go to /Applications/Xcode.app/Contents and rummage through that directory, you'll see it provides the command line utilities (the "toolchain") for each platform, libraries and headers for each platform, private frameworks for use within Xcode, resources, Interface Builder, etc.. Pretty much all related to the Xcode application, and the APIs specific to Xcode like XcodeKit that provide the API for build Xcode extensions, etc..
If you install the CommandLineUtilities, you'll get just the command line stuff, but, it still uses the system Foundation, etc..