7272/// * Exponential form always has 1 digit before the decimal point
7373/// * When present, a '.' is never the first or last character
7474/// * There is a consecutive range of integer values that can be
75- /// represented in double (-2^54...2^54). Never use exponential
76- /// form for integral numbers in this range.
75+ /// represented in any particular type (-2^54...2^54 for double).
76+ /// Never use exponential form for integral numbers in this range.
7777/// * Generally follow existing practice: Don't use use exponential
7878/// form for fractional values bigger than 10^-4; always write at
7979/// least 2 digits for an exponent.
9797///
9898// ----------------------------------------------------------------------------
9999
100+ // Float16 is not currently supported on Intel macOS.
101+ // (This will change once there's a fully-stable Float16
102+ // ABI on that platform.)
103+ #if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
100104// Implement the legacy ABI on top of the new one
105+ @_silgen_name ( " swift_float16ToString2 " )
106+ internal func _float16ToStringImpl2(
107+ _ textBuffer: UnsafeMutablePointer < UTF8 . CodeUnit > ,
108+ _ bufferLength: UInt ,
109+ _ value: Float16 ,
110+ _ debug: Bool ) -> UInt64 {
111+ // Code below works with raw memory.
112+ var buffer = unsafe MutableSpan< UTF8 . CodeUnit > ( _unchecked: textBuffer,
113+ count: Int ( bufferLength) )
114+ let textRange = Float16ToASCII ( value: value, buffer: & buffer)
115+ let textLength = textRange. upperBound - textRange. lowerBound
116+
117+ // Move the text to the start of the buffer
118+ if textRange. lowerBound != 0 {
119+ unsafe _memmove( dest: textBuffer,
120+ src: textBuffer + textRange. lowerBound,
121+ size: UInt ( truncatingIfNeeded: textLength) )
122+ }
123+ return UInt64 ( truncatingIfNeeded: textLength)
124+ }
125+ #endif
126+
101127@_silgen_name ( " swift_float32ToString2 " )
102128internal func _float32ToStringImpl2(
103129 _ textBuffer: UnsafeMutablePointer < UTF8 . CodeUnit > ,
@@ -140,7 +166,7 @@ internal func _float64ToStringImpl2(
140166 return UInt64 ( truncatingIfNeeded: textLength)
141167}
142168
143- #if !arch(x86_64)
169+ #if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64) )
144170internal func Float16ToASCII(
145171 value f: Float16 ,
146172 buffer utf8Buffer: inout MutableSpan < UTF8 . CodeUnit > ) -> Range < Int >
@@ -200,6 +226,12 @@ fileprivate func _Float16ToASCII(
200226 var firstDigit = 1
201227 var nextDigit = firstDigit
202228
229+ // Emit the text form differently depending on what range it's in.
230+ // We use `storeBytes(of:toUncheckedByteOffset:as:)` for most of
231+ // the output, but are careful to use the checked/safe form
232+ // `storeBytes(of:toByteOffset:as:)` for the last byte so that we
233+ // reliably crash if we overflow the provided buffer.
234+
203235 // Step 3: If it's < 10^-5, format as exponential form
204236 if binaryExponent < - 13 || ( binaryExponent == - 13 && significand < 0x1a38 ) {
205237 var decimalExponent = - 5
@@ -262,9 +294,10 @@ fileprivate func _Float16ToASCII(
262294 toUncheckedByteOffset: nextDigit,
263295 as: UInt8 . self)
264296 nextDigit &+= 1
265- unsafe buffer. storeBytes ( of: UInt8 ( truncatingIfNeeded: - decimalExponent % 10 &+ 0x30 ) ,
266- toUncheckedByteOffset: nextDigit,
267- as: UInt8 . self)
297+ // Last write on this branch, so use a safe checked store
298+ buffer. storeBytes ( of: UInt8 ( truncatingIfNeeded: - decimalExponent % 10 &+ 0x30 ) ,
299+ toByteOffset: nextDigit,
300+ as: UInt8 . self)
268301 nextDigit &+= 1
269302
270303 } else {
@@ -312,9 +345,10 @@ fileprivate func _Float16ToASCII(
312345
313346 if fractionPart == 0 {
314347 // Step 6: No fraction, so ".0" and we're done
315- unsafe buffer. storeBytes ( of: 0x30 ,
316- toUncheckedByteOffset: nextDigit,
317- as: UInt8 . self)
348+ // Last write on this branch, so use a checked store
349+ buffer. storeBytes ( of: 0x30 ,
350+ toByteOffset: nextDigit,
351+ as: UInt8 . self)
318352 nextDigit &+= 1
319353 } else {
320354 // Step 7: Emit the fractional part by repeatedly
@@ -328,15 +362,14 @@ fileprivate func _Float16ToASCII(
328362 while true {
329363 u = ( u & mask) &* 10
330364 l = ( l & mask) &* 10
331- // This actually overflows, but we only need the
332- // low-order bits, so it doesn't matter.
333- t = ( t & mask) &* 10
334365 uDigit = UInt8 ( truncatingIfNeeded: u >> 28 )
335366 lDigit = UInt8 ( truncatingIfNeeded: l >> 28 )
336367 if uDigit != lDigit {
368+ t = ( t & mask) &* 10
337369 break
338370 }
339-
371+ // This overflows, but we don't care at this point.
372+ t &*= 10
340373 unsafe buffer. storeBytes ( of: 0x30 &+ uDigit,
341374 toUncheckedByteOffset: nextDigit,
342375 as: UInt8 . self)
@@ -345,26 +378,28 @@ fileprivate func _Float16ToASCII(
345378 t &+= 1 << 27
346379 if ( t & mask) == 0 { // Exactly 1/2
347380 t = ( t >> 28 ) & ~ 1 // Round last digit even
348- // Without this next check, 0.015625 == 2^-6 prints
349- // as "0.01562" which does not round-trip correctly.
350- // With this, we get "0.01563" which does.
351- // It affects no other value.
352- if t <= lDigit && l > 0 {
381+ // Rounding `t` even can end up moving `t` below
382+ // `l`. Detect and correct for this possibility.
383+ // Exhaustive testing shows that the only input value
384+ // affected by this is 0.015625 == 2^-6, which
385+ // incorrectly prints as "0.01562" without this fix.
386+ if t < lDigit || ( t == lDigit && l > 0 ) {
353387 t += 1
354388 }
355389 } else {
356390 t >>= 28
357391 }
358- unsafe buffer. storeBytes ( of: UInt8 ( truncatingIfNeeded: 0x30 + t) ,
359- toUncheckedByteOffset: nextDigit,
360- as: UInt8 . self)
392+ // Last write on this branch, so use a checked store
393+ buffer. storeBytes ( of: UInt8 ( truncatingIfNeeded: 0x30 + t) ,
394+ toByteOffset: nextDigit,
395+ as: UInt8 . self)
361396 nextDigit &+= 1
362397 }
363398 }
364399 if f. sign == . minus {
365- unsafe buffer. storeBytes ( of: 0x2d ,
366- toUncheckedByteOffset : firstDigit &- 1 ,
367- as: UInt8 . self) // "-"
400+ buffer. storeBytes ( of: 0x2d ,
401+ toByteOffset : firstDigit &- 1 ,
402+ as: UInt8 . self) // "-"
368403 firstDigit &-= 1
369404 }
370405 return firstDigit..< nextDigit
@@ -982,15 +1017,6 @@ fileprivate func _Float64ToASCII(
9821017 unsafe buffer. storeBytes ( of: t,
9831018 toUncheckedByteOffset: nextDigit - 1 ,
9841019 as: UInt8 . self)
985- // Note: "exactly" 1/2 is a subtle point above; this
986- // determination relies on various roundings canceling
987- // out, and proving correctness requires proper
988- // testing. Testing so far has validated the
989- // correctness of this code. However, even if that
990- // were not true, this only affects whether we choose
991- // the theoretically-ideal even final digit when an
992- // odd final digit would otherwise satisfy all
993- // requirements.
9941020 } else {
9951021 let adjust = ( skew + oneHalf) >> ( 64 - adjustIntegerBits)
9961022 var t = unsafe buffer. unsafeLoad ( fromUncheckedByteOffset: nextDigit - 1 ,
@@ -1175,6 +1201,7 @@ fileprivate let asciiDigitTable: InlineArray<100, UInt16> = [
11751201 0x3539 , 0x3639 , 0x3739 , 0x3839 , 0x3939
11761202]
11771203
1204+ // The constants below assume we're on a little-endian processor
11781205fileprivate func infinity( buffer: inout MutableRawSpan, sign: FloatingPointSign) - > Range< Int> {
11791206 if sign == . minus {
11801207 buffer. storeBytes ( of: 0x666e692d , toByteOffset: 0 , as: UInt32 . self) // "-inf"
@@ -1233,7 +1260,7 @@ fileprivate func nan_details(buffer: inout MutableRawSpan,
12331260 // value is a NaN of some sort
12341261 var i = 0
12351262 if sign == . minus {
1236- buffer. storeBytes ( of: 0x2d , toByteOffset: 0 , as: UInt8 . self)
1263+ buffer. storeBytes ( of: 0x2d , toByteOffset: 0 , as: UInt8 . self) // "-"
12371264 i = 1
12381265 }
12391266 if quiet {
0 commit comments