Skip to content

Commit 302e37a

Browse files
committed
Fix and improve numeric-literals.md
1 parent 93df033 commit 302e37a

File tree

1 file changed

+37
-8
lines changed

1 file changed

+37
-8
lines changed

docs/docs/reference/changed-features/numeric-literals.md

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,47 +24,54 @@ how large they can be.
2424

2525
The meaning of a numeric literal is determined as follows:
2626

27-
- If the literal ends with `l` or `L`, it is a `Long` integer (and must fit
28-
in its legal range).
29-
- If the literal ends with `f` or `F`, it is a single precision floating point number of type `Float`.
30-
- If the literal ends with `d` or `D`, it is a double precision floating point number of type `Double`.
27+
- If the literal ends with `l` or `L`, it is a `Long` integer (and must fit in its legal range).
28+
- If the literal ends with `f` or `F`, it is a single precision floating point number of type `Float`.
29+
- If the literal ends with `d` or `D`, it is a double precision floating point number of type `Double`.
3130

3231
In each of these cases the conversion to a number is exactly as in Scala 2 or in Java. If a numeric literal does _not_ end in one of these suffixes, its meaning is determined by the expected type:
3332

34-
1. If the expected type is `Int`, `Long`, `Float`, or `Double`, the literal is
33+
1. If the expected type is `Int`, `Long`, `Float`, or `Double`, the literal is
3534
treated as a standard literal of that type.
36-
2. If the expected type is a fully defined type `T` that has a given instance of type
35+
2. If the expected type is a fully defined type `T` that has a given instance of type
3736
`scala.util.FromDigits[T]`, the literal is converted to a value of type `T` by passing it as an argument to
3837
the `fromDigits` method of that instance (more details below).
39-
3. Otherwise, the literal is treated as a `Double` literal (if it has a decimal point or an
38+
3. Otherwise, the literal is treated as a `Double` literal (if it has a decimal point or an
4039
exponent), or as an `Int` literal (if not). (This last possibility is again as in Scala 2 or Java.)
4140

4241
With these rules, the definition
42+
4343
```scala
4444
val x: Long = -10_000_000_000
4545
```
46+
4647
is legal by rule (1), since the expected type is `Long`. The definitions
48+
4749
```scala
4850
val y: BigInt = 0x123_abc_789_def_345_678_901
4951
val z: BigDecimal = 111222333444.55
5052
```
53+
5154
are legal by rule (2), since both `BigInt` and `BigDecimal` have `FromDigits` instances
5255
(which implement the `FromDigits` subclasses `FromDigits.WithRadix` and `FromDigits.Decimal`, respectively).
5356
On the other hand,
57+
5458
```scala
5559
val x = -10_000_000_000
5660
```
61+
5762
gives a type error, since without an expected type `-10_000_000_000` is treated by rule (3) as an `Int` literal, but it is too large for that type.
5863

5964
### The FromDigits Trait
6065

6166
To allow numeric literals, a type simply has to define a `given` instance of the
6267
`scala.util.FromDigits` type class, or one of its subclasses. `FromDigits` is defined
6368
as follows:
69+
6470
```scala
6571
trait FromDigits[T]:
6672
def fromDigits(digits: String): T
6773
```
74+
6875
Implementations of the `fromDigits` convert strings of digits to the values of the
6976
implementation type `T`.
7077
The `digits` string consists of digits between `0` and `9`, possibly preceded by a
@@ -74,6 +81,7 @@ the string is passed to `fromDigits`.
7481
The companion object `FromDigits` also defines subclasses of `FromDigits` for
7582
whole numbers with a given radix, for numbers with a decimal point, and for
7683
numbers that can have both a decimal point and an exponent:
84+
7785
```scala
7886
object FromDigits:
7987

@@ -95,6 +103,7 @@ object FromDigits:
95103
*/
96104
trait Floating[T] extends Decimal[T]
97105
```
106+
98107
A user-defined number type can implement one of those, which signals to the compiler
99108
that hexadecimal numbers, decimal points, or exponents are also accepted in literals
100109
for this type.
@@ -104,6 +113,7 @@ for this type.
104113
`FromDigits` implementations can signal errors by throwing exceptions of some subtype
105114
of `FromDigitsException`. `FromDigitsException` is defined with three subclasses in the
106115
`FromDigits` object as follows:
116+
107117
```scala
108118
abstract class FromDigitsException(msg: String) extends NumberFormatException(msg)
109119

@@ -115,17 +125,22 @@ class MalformedNumber(msg: String = "malformed number literal") extends FromDigi
115125
### Example
116126

117127
As a fully worked out example, here is an implementation of a new numeric class, `BigFloat`, that accepts numeric literals. `BigFloat` is defined in terms of a `BigInt` mantissa and an `Int` exponent:
128+
118129
```scala
119130
case class BigFloat(mantissa: BigInt, exponent: Int):
120131
override def toString = s"${mantissa}e${exponent}"
121132
```
133+
122134
`BigFloat` literals can have a decimal point as well as an exponent. E.g. the following expression
123135
should produce the `BigFloat` number `BigFloat(-123, 997)`:
136+
124137
```scala
125138
-0.123E+1000: BigFloat
126139
```
140+
127141
The companion object of `BigFloat` defines an `apply` constructor method to construct a `BigFloat`
128142
from a `digits` string. Here is a possible implementation:
143+
129144
```scala
130145
object BigFloat:
131146
import scala.util.FromDigits
@@ -149,13 +164,16 @@ object BigFloat:
149164
(intPart, givenExponent)
150165
BigFloat(BigInt(intPart), exponent)
151166
```
167+
152168
To accept `BigFloat` literals, all that's needed in addition is a `given` instance of type
153169
`FromDigits.Floating[BigFloat]`:
170+
154171
```scala
155172
given FromDigits: FromDigits.Floating[BigFloat] with
156173
def fromDigits(digits: String) = apply(digits)
157174
end BigFloat
158175
```
176+
159177
Note that the `apply` method does not check the format of the `digits` argument. It is
160178
assumed that only valid arguments are passed. For calls coming from the compiler
161179
that assumption is valid, since the compiler will first check whether a numeric
@@ -164,35 +182,42 @@ literal has the correct format before it gets passed on to a conversion method.
164182
### Compile-Time Errors
165183

166184
With the setup of the previous section, a literal like
185+
167186
```scala
168187
1e10_0000_000_000: BigFloat
169188
```
189+
170190
would be expanded by the compiler to
191+
171192
```scala
172193
BigFloat.FromDigits.fromDigits("1e100000000000")
173194
```
195+
174196
Evaluating this expression throws a `NumberTooLarge` exception at run time. We would like it to
175197
produce a compile-time error instead. We can achieve this by tweaking the `BigFloat` class
176198
with a small dose of metaprogramming. The idea is to turn the `fromDigits` method
177199
into a macro, i.e. make it an inline method with a splice as right hand side.
178200
To do this, replace the `FromDigits` instance in the `BigFloat` object by the following two definitions:
201+
179202
```scala
180203
object BigFloat:
181204
...
182205

183206
class FromDigits extends FromDigits.Floating[BigFloat]:
184207
def fromDigits(digits: String) = apply(digits)
185208

186-
given FromDigits:
209+
given FromDigits with
187210
override inline def fromDigits(digits: String) = ${
188211
fromDigitsImpl('digits)
189212
}
190213
```
214+
191215
Note that an inline method cannot directly fill in for an abstract method, since it produces
192216
no code that can be executed at runtime. That is why we define an intermediary class
193217
`FromDigits` that contains a fallback implementation which is then overridden by the inline
194218
method in the `FromDigits` given instance. That method is defined in terms of a macro
195219
implementation method `fromDigitsImpl`. Here is its definition:
220+
196221
```scala
197222
private def fromDigitsImpl(digits: Expr[String])(using ctx: Quotes): Expr[BigFloat] =
198223
digits.value match
@@ -207,6 +232,7 @@ implementation method `fromDigitsImpl`. Here is its definition:
207232
'{apply($digits)}
208233
end BigFloat
209234
```
235+
210236
The macro implementation takes an argument of type `Expr[String]` and yields
211237
a result of type `Expr[BigFloat]`. It tests whether its argument is a constant
212238
string. If that is the case, it converts the string using the `apply` method
@@ -218,10 +244,13 @@ The interesting part is the `catch` part of the case where `digits` is constant.
218244
If the `apply` method throws a `FromDigitsException`, the exception's message is issued as a compile time error in the `ctx.error(ex.getMessage)` call.
219245

220246
With this new implementation, a definition like
247+
221248
```scala
222249
val x: BigFloat = 1234.45e3333333333
223250
```
251+
224252
would give a compile time error message:
253+
225254
```scala
226255
3 | val x: BigFloat = 1234.45e3333333333
227256
| ^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)