Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FS-1142] Extended numeric literals #770

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions RFCs/FS-1142-Extended-numeric-literal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# F# RFC FS-1142 - Extended numeric literal

The design suggestion [Underscores in numeric literals after prefix and before suffix](https://github.com/fsharp/fslang-suggestions/issues/718), [Extend custom numeric types to support floating point literals](https://github.com/fsharp/fslang-suggestions/issues/445) and [Hexadecimal, octal and binary custom numeric literals](https://github.com/fsharp/fslang-suggestions/issues/754) has been marked "approved in principle".

This RFC covers the detailed proposal for this suggestion.

- [x] Suggestion: [Underscores in numeric literals after prefix and before suffix](https://github.com/fsharp/fslang-suggestions/issues/718), [Extend custom numeric types to support floating point literals](https://github.com/fsharp/fslang-suggestions/issues/445) and [Hexadecimal, octal and binary custom numeric literals](https://github.com/fsharp/fslang-suggestions/issues/754)
- [x] Approved in principle
- [ ] [Implementation](https://github.com/dotnet/fsharp/pull/17242)
ijklam marked this conversation as resolved.
Show resolved Hide resolved
- [ ] Design Review Meeting(s) with @dsyme and others invitees
- [Discussion](https://github.com/fsharp/fslang-design/discussions/769)

# Summary

This RFC will allow the following things:

- Underscores in numeric literals after prefix and before suffix like `0x_1` or `1_l` or mixed them up like `0x_1_l`.
ijklam marked this conversation as resolved.
Show resolved Hide resolved
- Hexadecimal, octal, binary and floating point custom numeric literals like `0x1I` or `1.0G`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel strongly that we should put the floating point part (possibly with decimals) in a separate RFC. It is significantly different to extend the domain, vs, just updating a bit of syntax (i.e., 0x and 0b are just syntax).

Can you also expand if the previous point applies to custom literals as well, and if, in that case, the trailing I or G can have an underscore before it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. there can be some underscores before I or G


# Motivation

Make the language more consistent and easier to read. Enhance the custom numeric literals feature by supporting not only integers but also floats.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

floats: please put that in a separate RFC (and later also, PR).


# Detailed design

1. Underscores can now be placed after prefix (`0x`, `0o`, `0b`) and before suffix (eg. `UL`, `y`, `m`, `f`). These are forbidden [previously](https://github.com/fsharp/fslang-design/blob/main/FSharp-4.1/FS-1005-underscores-in-numeric-literals.md#detailed-design).
ijklam marked this conversation as resolved.
Show resolved Hide resolved

ijklam marked this conversation as resolved.
Show resolved Hide resolved
```fsharp
let pi1 = 3_.1415F // Invalid cannot put underscores adjacent to a decimal point
let pi2 = 3._1415F // Invalid cannot put underscores adjacent to a decimal point
let socialSecurityNumber1 = 999_99_9999_L // OK (Invalid *previously*)
ijklam marked this conversation as resolved.
Show resolved Hide resolved

let x1 = _52 // This is an identifier, not a numeric literal
let x2 = 5_2 // OK (decimal literal)
ijklam marked this conversation as resolved.
Show resolved Hide resolved
let x3 = 52_ // Invalid cannot put underscores at the end of a literal
let x4 = 5_______2 // OK (decimal literal)
ijklam marked this conversation as resolved.
Show resolved Hide resolved

let x5 = 0_x52 // Invalid cannot put underscores in the 0x radix prefix
let x6 = 0x_52 // OK (Invalid *previously*)
let x7 = 0x5_2 // OK (hexadecimal literal)
ijklam marked this conversation as resolved.
Show resolved Hide resolved
let x8 = 0x52_ // Invalid cannot put underscores at the end of a number

// In contrast to Java, literals with leading zeros are decimal in F#.
ijklam marked this conversation as resolved.
Show resolved Hide resolved
let x9 = 0_52 // OK (decimal literal)
let x10 = 05_2 // OK (decimal literal)
let x11 = 052_ // Invalid cannot put underscores at the end of a number
ijklam marked this conversation as resolved.
Show resolved Hide resolved

// To create an octal literal, prefix it with '0o' similar to hexadezimal literals. The same rules apply:
ijklam marked this conversation as resolved.
Show resolved Hide resolved
let x12 = 0_o52 // Invalid cannot put underscores in the 0o radix prefix
let x13 = 0o_52 // OK (Invalid *previously*)
let x14 = 0o5_2 // OK (octal literal)
let x15 = 0o52_ // Invalid cannot put underscores at the end of a number
ijklam marked this conversation as resolved.
Show resolved Hide resolved
```

2. Allow number prefix (`0x`, `0o`, `0b`) before integer custom numeric literals.
ijklam marked this conversation as resolved.
Show resolved Hide resolved

```fsharp
let x1 = 0x123I // big int (291)
let x2 = 0o123I // big int (83)
let x3 = 0b1010I // big int (10)
```

The number prefix **will not be removed** from the string sended to the `FromString` of the numeric literal module. This may be a break change.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is not a breaking change, because it was previously not possible to do this.

Suggested change
The number prefix **will not be removed** from the string sended to the `FromString` of the numeric literal module. This may be a break change.
The number prefix is retained when passed as argument to `FromString` of the numeric literal module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although the bignum (123I) already known how to deal with the number prefix (0x123I is now ok without changing the library), other custom numeric literals may not know that. This might break their function, so I think it is a breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is, this is not a break change for existing source code, but it is for library.


```fsharp
module NumericLiteralG =
let FromString s = s

let x4 = 0x_99_9999_9999_9999_9999G // x4 will be "0x999999999999999999"
ijklam marked this conversation as resolved.
Show resolved Hide resolved
```

3. Allow floating point custom numeric literals.

This will require the numeric literal module contains two new functions:

```fsharp
val FromFloat: float -> 'CustomNumber
val FromFloatString: string -> 'CustomNumber
```

According to [this comment](https://github.com/fsharp/fslang-suggestions/issues/445#issuecomment-596902041),

- When the literal has <= 15 significant figures[^1] and its exponent is within -300 to 300, the compiler will call `FromFloat` with the parsed `float` number
- Or the compiler will call `FromFloatString` with the original string

[^1]: The first none zero figure to last none zero figure, without the dot. Can be match by the Regex:

```regex
^-?0*(?<number>\d+\.?\d*?)0*(?:$|[eE][+-]?(?<exp>\d+))
```

```fsharp
module NumericLiteralG =
let FromFloat (s: float) = s
let FromFloatString (s: string) = s

let x1 = 123.456e-10G // x1: float = 1.23456e-08
let x2 = 123.456789123456789123e-10G // x2: string = "123.456789123456789123e-10"
```
Comment on lines +74 to +101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put this in a separate RFC, it is rather separate from the other things here (it shares the same subject, but smaller RFCs with less scope have a larger change of "making it", same is true for implementation PRs).


# Drawbacks

- Custom numeric literals module might harder to write.

- May introduce break change.
Comment on lines +105 to +107
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the breaking change?

Despite my comment above, I think you are right: if we pass the strings with the prefix, this may lead to existing modules not understanding this syntax. This can be explained in the section below (i.e.: if existing modules encounter the new syntax, they may fail).

However, existing code will never fail, so in that sense, it is not a breaking change.


# Alternatives

- For number prefix (`0x`, `0o`, `0b`) before integer custom numeric literals, we might introduce a new `FromIntegerString` to avoid the break change.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's possible, but I think it is a bad idea, precisely because currently, only integers are allowed anyway.

- Or firstly parse it to `bigint` then `ToString` to obtain a literal without prefix.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a much better suggestion. In fact, you don't even have to do that. Conversion is rather trivial and can be done on-the-fly.

This would certainly be my preference and would avoid any burden on people writing custom numerals modules.


# Compatibility

Please address all necessary compatibility questions:

* Is this a breaking change?
Maybe
Comment on lines +116 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to avoid this, if possible, see my comments and trains of thought above.


* What happens when previous versions of the F# compiler encounter this design addition as source code?
Can write numeric literal module with new functions in the source code, but cannot use these new numeric literal grammar.

* What happens when previous versions of the F# compiler encounter this design addition in compiled binaries?
Cannot use the new numeric literal grammar.
Comment on lines +121 to +125
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's revisit this after we have a decision.


# Pragmatics

## Tooling

Please list the reasonable expectations for tooling for this feature, including any of these:

* Colorization

Might need to change the color schema of the numeric literals.
Comment on lines +133 to +135
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably not going to be a problem, as the syntax highlighter uses the parser results. If the parser says something is a numeric literal, it will be colored as such.

But certainly something to test before merging this change


# Unresolved questions

- Should we introduce a new `FromIntegerString` or use any way to remove number prefix from custom integer literal string passed to `FromString`?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion is: no