Arguably this is a bug that’s worth a source-breaking change to fix. -0 as Float
not producing a negative zero is just wrong.
Source-breaking - yes. Worth "fixing" - I'd also say yes.
let a: Double = -0
print(a.sign) // plus
let b: Double = -0.0
print(b.sign) // minus
let c: Double = -(0)
print(c.sign) // minus
let d: Double = -(0.0)
print(d.sign) // minus
let O: Double = 0
let e: Double = -O
print(e.sign) // minus
let f: Double = -(O)
print(f.sign) // minus
from TSPL:
Grammar of a literal
literal → numeric-literal | string-literal | regular-expression-literal | boolean-literal | nil-literal
numeric-literal →
-
opt integer-literal |-
opt floating-point-literal
the TSPL grammar has had mistakes in it in the past, but in this instance i think it (and the parser implementation) is correct, and consistent with how other literal grammars (e.g., JSON) handle prefix +
/-
.
in my view, that swift generates a function call out of prefix +
is the aberration.
also, i think it is weird that swift considers +
inside a floating point literal to be part of the float literal, but it does not consider +
prefixed to an integer literal to be part of the integer literal.
floating-point-e →
e
|E
floating-point-p →
p
|P
sign →
+
|-
parsing -0
as (-)(0 as some ExpressibleByIntegerLiteral)
breaks integer literal initializers. in particular, it will no longer be possible to express:
let min:Int8 = -128
because init(integerLiteral:)
would receive (128 as Int8)
, and that would overflow the bit width.
EDIT: just saw that @John_McCall noticed the same thing.
On the contrary, Swift language should be unaware of prefix minus or prefix plus or any other prefix operator. Plus on a custom type (which can be integer literal expressible among other things) can do arbitrary things instead of returning self
unchanged, and prefix minus should be parsed as a proper operator to avoid the anomalies mentioned above or similar, IMHO.
Looks a lesser evil to me.
How often do you have to write "-2146483648" (or 2146483647 FTM) †? Perhaps you can do the same and use .min / .max instead of -128 / 127
(† - yes, there's a typo and that's to stress the point.)
Edit: IIRC (yep, e.g. here, search for SCHAR_MIN), C used the notion of INT_MIN / INT_MAX not only to account for different size in bits, but to be compatible with weird one's complement architectures where INT8_MIN is -127
. Ok, ok, we'll probably never need to support that in Swift. Right?
Edit2: I used to ask this question (or rather its C equivalent) in the interviews
func isqrt(_ x: Int) -> Int {
precondition(x >= 0)
return Int(sqrt(Double(x)))
}
// 🐞 not so safe 🐞
func safeSqrt(_ x: Int) -> Int {
var x = x
if x < 0 { x = -x }
return isqrt(x)
}
Edit3: another funny + / - asymmetry:
enum SpecialNumbers: Double {
case negativeZero = -0.0
case zero = 0.0
case positiveZero = +0.0 // 🛑 Enum case must declare a raw value when the preceding raw value is not an integer
}
We are definitely not going to cause -128 as Int8
to fail compilation. That’s utterly out of the question; it’s also, incidentally, not something that is in the scope of this amendment review.
Actually, that's not the case, I think—not if we do deliberately something like what we are trying to undo here (which was done accidentally):
If static prefix func - (_: StaticBigInt) -> Int8
overloads are added that return values of each concrete fixed-width integer type, they ought to be favored and permit overflow checking to be deferred to the result of negation, allowing the full range of values to be expressed as literals without the need for special handling of -
for literals.
(Some preliminary fiddling on a playground suggests the compiler doesn't like that too much at the moment for reasons unclear.)
Floating-point literals have the same issue as integer literals.
sign →
+
|-
The sign is only used in the grammar of a decimal-exponent or hexadecimal-exponent.
struct Number: ExpressibleByFloatLiteral {
init(floatLiteral: Double) {}
static prefix func + (_ rhs: Self) -> Self { rhs }
}
let a = -7e+2 //-> `-700 as Double`
let b = +6e-2 //-> `0.06 as Number`
let c = a * b
// ^
// error: cannot convert value of type 'Number' to expected argument type 'Double'
Here's another interesting quirk from the way negative literals are handled in Swift: while the negative sign is sometimes considered part of the literal, it still retains the same precedence as the negation operator. This leads to interesting cases like the one below.
extension Numeric {
func incremented() -> Self {
return self + 1
}
}
-13.incremented() // -14
(-13).incremented() // -12
-(13.incremented()) // -14
I don’t think this is really a quirk. The right way to understand the behavior is as I described it above, that the operator behaves specially when directly applied to an integer literal. The grammar is wrong to say that it’s grammatically part of the literal.
That might be the right way to think about it, but I don't think most Swift programmers will think about it that way. This has led to confusion before. And not only does the grammar get this subtlety wrong — the syntax highlighters for the Swift Playgrounds app and Visual Studio Code also get it wrong, highlighting this expression incorrectly.
Personally, I'd be in favor of adding a warning to expressions like -13.incremented()
that can be suppressed by adding parentheses around a subexpression e.g. -(13.incremented())
or (-13).incremented()
.
Unless your argument is that -x.incremented()
is crystal clear but -13.incremented()
isn't, this isn't about literals and the special quirk here.
As was rightfully noted the scope of this review is the amendment to StaticBigInt (specifically removing the "+" operation from it). We should move all other discussions (e.g. those related to "prefix -" peculiarities) elsewhere (and yes, they deserve being discussed).
i cannot reproduce this on VSCode using the 5.8 toolchain and latest VSCode swift extension.
Thank you everyone for participating in this review! The amendment has been accepted:
Holly Borla
Review Manager