Float Literals¶
# Decimal floats (64-bit)
pi = 3.14159
half = 0.5
negative = -2.718
# Leading decimal point is allowed (like Python and C#)
valid1 = 0.5
valid2 = 5.0
valid3 = .5 # Same as 0.5
# ❌ Invalid - trailing decimal point without digit
# invalid = 5. # ERROR: use 5.0 instead
# Underscores for readability (optional)
precise = 3.141_592_653
Type Inference¶
- Float literals with decimal point are inferred as
float64(System.Double) - Suffix notation for explicit typing (optional):
forFforfloat32(System.Single):3.14fdorDforfloat64(System.Double):3.14d(redundant but allowed)morMfordecimal(System.Decimal):3.14m
A suffix means the same on every spelling of a float — .5f is 0.5f, and 1e5f and 1_0.2_5m
take it too. The integer suffixes L, u and ul (see Integer Literals)
belong to integers; on a float literal they are refused (SPY0023), as in C#:
# x = 3.14L # ERROR (SPY0023): Invalid numeric suffix: L (a float literal accepts only the float suffixes f, d and m)
# y = 1e5u # ERROR (SPY0023): Invalid numeric suffix: u (a float literal accepts only the float suffixes f, d and m)
Narrowing an Unsuffixed Literal to float32 or decimal¶
An unsuffixed float literal may narrow to float32 or decimal at every store
position — the same rule as integer constants (§10.2.11 above). The literal is re-typed
and emitted with the appropriate suffix (f for float32, m for decimal):
from System.Numerics import Vector2
class Holder:
ratio: float32 = 0.5 # field declaration
def ret() -> float32:
return 0.1 # return
def takes(x: float32 = 0.1) -> None: # parameter default
print(x)
def main() -> None:
x: float32 = 0.1 # declaration
x = 0.25 # plain store
print(x) # 0.25
takes(0.5) # argument
print(ret()) # 0.1
xs: list[float32] = [0.5] # collection-literal element
v: Vector2 = Vector2(1.0, 2.0) # CLR argument
print(v.X) # 1.0
The same rule applies to decimal, at the same positions:
def money() -> decimal:
return 0.1 # return
def taxed(rate: decimal = 0.2) -> None: # parameter default
print(rate)
def main() -> None:
d: decimal = 1.5 # declaration
print(d) # 1.5
e: decimal = 1.5e2 # exponent form narrows too
print(e) # 150
taxed(0.25) # argument -> 0.25
print(money()) # 0.1
ds: list[decimal] = [0.5] # collection-literal element
print(ds[0]) # 0.5
Two limits:
- Only a literal narrows. A
float64-typed expression or variable still requires an explicit cast:x: float32 = yis SPY0220 fory: float. - The value must be within
float32's range.x: float32 = 1e40is SPY0220, not a silent infinity. The same holds fordecimal, whose range is about ±7.9e28:d: decimal = 1e29is SPY0220, and so does a non-zero literal below decimal's smallest non-zero value:d: decimal = 1e-30is SPY0220.
This is a deliberate divergence from C#, which rejects float f = 0.1; outright. Precision loss
within range is accepted — writing the annotation is taken as asking for it.
A Literal Must Fit Its Own Type¶
Whatever its spelling, a float literal must have a finite value in the type it resolves to —
float64 unsuffixed, float32 with f, decimal with m. A literal outside that range is
refused at compile time with SPY0210, the code an out-of-range integer literal draws, and never
carried as an infinity. Python reads 1e400 as inf; Sharpy refuses it, because a silent infinity
would flow on through arithmetic.
def main() -> None:
big = 1.7976931348623157e308 # float64's largest value
print(big) # 1.7976931348623157e+308
f = 3.4028235e38f # float32's largest value
print(f) # 3.4028235e+38
m = 79228162514264337593543950335m # decimal's largest value
print(m) # 79228162514264337593543950335
# x = 1e400 # ERROR (SPY0210): Float literal '1e400' is too large for 'double'
# y = 1e39f # ERROR (SPY0210): Float literal '1e39' is too large for 'float'
# z = 1e29m # ERROR (SPY0210): Float literal '1e29' is too large for 'decimal'
tiny = 1e-28m # decimal's smallest non-zero value
print(tiny) # 0.0000000000000000000000000001
print(1e-400) # 0.0
# w = 1e-30m # ERROR (SPY0210): Float literal '1e-30' is too small for 'decimal': its value would be zero
A negated literal is checked by its digits, so -1e400 is refused the same way. Below the smallest
non-zero value the types part ways. A float32 or float64 literal too small to represent is IEEE
zero, not out of range: 1e-400 is 0.0, as in Python. A non-zero decimal literal whose value
would be zero (1e-30m, 1.5e-29m; decimal's smallest non-zero magnitude is 1e-28) is refused
with SPY0210, on the suffixed and the slot-narrowed spelling alike, because a decimal is chosen for
exact values and a silent zero is the wrong one. A literal with more than decimal's 28–29
significant digits is rounded, not refused (the decimal-literal-28-digit-precision deviation).
Implementation - ✅ Native - Direct mapping to C# float literals with compiler-inserted suffixes.