Add appendix to spec which specifies JavaScript integer behavior.

Also update the `int` class documentation.

Change-Id: I2de6c62aa9642c18134effa8187a12902d5f2259
Reviewed-on: https://dart-review.googlesource.com/58204
Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
Reviewed-by: Leaf Petersen <leafp@google.com>
This commit is contained in:
Lasse R.H. Nielsen 2018-06-18 11:17:57 +00:00 committed by commit-bot@chromium.org
parent dfeaf79cef
commit 87131780cb
2 changed files with 117 additions and 62 deletions

View file

@ -58,6 +58,8 @@
% - Specify configurable imports.
% - Specify the dynamic type of the Iterable/Future/Stream returned from
% invocations of functions marked sync*/async/async*.
% - Add appendix listing the major differences between 64-bit integers
% and JavaScript integers.
%
% 1.15
% - Change how language specification describes control flow.
@ -10482,6 +10484,43 @@ Example: my\_favorite\_library.
\end{itemize}
}
\section*{Appendix: Integer Implementations}
\LMLabel{integerImplementations}
\commentary{
The \code{int} type represents integers.
The specification is written with 64-bit two's complement integers as the
intended implementation, but when Dart is compiled to JavaScript,
the implementation of \code{int} will instead use the JavaScript
number type.
This introduces a number of differencs:
\begin{itemize}
\item Valid values of JavaScript \code{int} are any
IEEE-754 64-bit floating point number with no fractional part.
This includes positive and negative {\em infinity},
which can be reached by overflowing
(integer division by zero is still not allowed).
Otherwise valid integer literals (including any leading minus sign)
that represent invalid JavaScript \code{int} values
cannot be compiled to JavaScript.
Operations on integers may lose precision since 64-bit floating point numbers
are limited to 53 significant bits.
\item JavaScript \code{int} instances also implement \code{double},
and integer-valued \code{double} instances also implement \code{int}.
The \code{int} and \code{double} class are still separate subclasses of the
class \code{num}, but {\em instances} of either class that represent an integer,
act as if they are actually instances of a common subclass implementing both
\code{int} and \code{double}. Fractional numbers only implement \code{double}.
\item Bitwise operations on integers (and, or, xor, negate and shifts)
all truncate the operands to 32-bit values.
\item The \code{identical} method cannot distinguish the values $0.0$ and $-0.0$,
and it cannot recognize any {\em NaN} value as identical to itself.
For efficiency, the \code{identical} operation uses the JavaScript \code{===}
operator.
\end{itemize}
}
\end{document}
[Text after \end{document} is ignored, hence we do not need "%"]

View file

@ -5,30 +5,36 @@
part of dart.core;
/**
* An arbitrarily large integer.
* An integer number.
*
* **Note:** When compiling to JavaScript, integers are
* implemented as JavaScript numbers. When compiling to JavaScript,
* integers are therefore restricted to 53 significant bits because
* all JavaScript numbers are double-precision floating point
* values. The behavior of the operators and methods in the [int]
* The default implementation of `int` is 64-bit two's complement integers
* with operations that wrap to that range on overflow.
*
* **Note:** When compiling to JavaScript, integers are restricted to valus
* that can be represented exactly by double-precision floating point values.
* The available integer values include all integers between -2^53 and 2^53,
* and some integers with larger magnitude. That includes some integers larger
* than 2^63.
* The behavior of the operators and methods in the [int]
* class therefore sometimes differs between the Dart VM and Dart code
* compiled to JavaScript.
* compiled to JavaScript. For example, the bitwise operators truncate their
* operands to 32-bit integers when compiled to JavaScript.
*
* It is a compile-time error for a class to attempt to extend or implement int.
* Classes cannot extend, implement, or mix in `int`.
*/
abstract class int extends num {
/**
* Returns the integer value of the given environment declaration [name].
*
* The result is the same as would be returned by:
*
* int.parse(const String.fromEnvironment(name, defaultValue: ""),
* (_) => defaultValue)
*
* ```
* int.tryParse(const String.fromEnvironment(name, defaultValue: ""))
* ?? defaultValue
* ```
* Example:
*
* const int.fromEnvironment("defaultPort", defaultValue: 80)
* ```
* const int.fromEnvironment("defaultPort", defaultValue: 80)
* ```
*/
// The .fromEnvironment() constructors are special in that we do not want
// users to call them using "new". We prohibit that by giving them bodies
@ -157,17 +163,18 @@ abstract class int extends num {
*
* To find the number of bits needed to store the value as a signed value,
* add one, i.e. use `x.bitLength + 1`.
* ```
* x.bitLength == (-x-1).bitLength
*
* x.bitLength == (-x-1).bitLength
*
* 3.bitLength == 2; // 00000011
* 2.bitLength == 2; // 00000010
* 1.bitLength == 1; // 00000001
* 0.bitLength == 0; // 00000000
* (-1).bitLength == 0; // 11111111
* (-2).bitLength == 1; // 11111110
* (-3).bitLength == 2; // 11111101
* (-4).bitLength == 2; // 11111100
* 3.bitLength == 2; // 00000011
* 2.bitLength == 2; // 00000010
* 1.bitLength == 1; // 00000001
* 0.bitLength == 0; // 00000000
* (-1).bitLength == 0; // 11111111
* (-2).bitLength == 1; // 11111110
* (-3).bitLength == 2; // 11111101
* (-4).bitLength == 2; // 11111100
* ```
*/
int get bitLength;
@ -175,21 +182,22 @@ abstract class int extends num {
* Returns the least significant [width] bits of this integer as a
* non-negative number (i.e. unsigned representation). The returned value has
* zeros in all bit positions higher than [width].
*
* (-1).toUnsigned(5) == 31 // 11111111 -> 00011111
*
* ```
* (-1).toUnsigned(5) == 31 // 11111111 -> 00011111
* ```
* This operation can be used to simulate arithmetic from low level languages.
* For example, to increment an 8 bit quantity:
*
* q = (q + 1).toUnsigned(8);
*
* ```
* q = (q + 1).toUnsigned(8);
* ```
* `q` will count from `0` up to `255` and then wrap around to `0`.
*
* If the input fits in [width] bits without truncation, the result is the
* same as the input. The minimum width needed to avoid truncation of `x` is
* given by `x.bitLength`, i.e.
*
* x == x.toUnsigned(x.bitLength);
* ```
* x == x.toUnsigned(x.bitLength);
* ```
*/
int toUnsigned(int width);
@ -199,24 +207,26 @@ abstract class int extends num {
* to fit in [width] bits using an signed 2-s complement representation. The
* returned value has the same bit value in all positions higher than [width].
*
* V--sign bit-V
* 16.toSigned(5) == -16 // 00010000 -> 11110000
* 239.toSigned(5) == 15 // 11101111 -> 00001111
* ^ ^
*
* ```
* V--sign bit-V
* 16.toSigned(5) == -16 // 00010000 -> 11110000
* 239.toSigned(5) == 15 // 11101111 -> 00001111
* ^ ^
* ```
* This operation can be used to simulate arithmetic from low level languages.
* For example, to increment an 8 bit signed quantity:
*
* q = (q + 1).toSigned(8);
*
* ```
* q = (q + 1).toSigned(8);
* ```
* `q` will count from `0` up to `127`, wrap to `-128` and count back up to
* `127`.
*
* If the input value fits in [width] bits without truncation, the result is
* the same as the input. The minimum width needed to avoid truncation of `x`
* is `x.bitLength + 1`, i.e.
*
* x == x.toSigned(x.bitLength + 1);
* ```
* x == x.toSigned(x.bitLength + 1);
* ```
*/
int toSigned(int width);
@ -271,8 +281,8 @@ abstract class int extends num {
* Returns a String-representation of this integer.
*
* The returned string is parsable by [parse].
* For any `int` [:i:], it is guaranteed that
* [:i == int.parse(i.toString()):].
* For any `int` `i`, it is guaranteed that
* `i == int.parse(i.toString())`.
*/
String toString();
@ -289,7 +299,7 @@ abstract class int extends num {
/**
* Parse [source] as a, possibly signed, integer literal and return its value.
*
* The [source] must be a non-empty sequence of base-[radix] digits,
* The [source] must be either a non-empty sequence of base-[radix] digits,
* optionally prefixed with a minus or plus sign ('-' or '+').
*
* The [radix] must be in the range 2..36. The digits used are
@ -299,30 +309,36 @@ abstract class int extends num {
*
* If no [radix] is given then it defaults to 10. In this case, the [source]
* digits may also start with `0x`, in which case the number is interpreted
* as a hexadecimal literal, which effectively means that the `0x` is ignored
* and the radix is instead set to 16.
* as a hexadecimal integer literal,
* When `int` is implemented by 64-bit signed integers,
* hexadecimal integer literals may represent values larger than
* 2<sup>63</sup>, in which case the value is parsed as if it is an
* *unsigned* number, and the resulting value is the corresponding
* signed integer value.
*
* For any int [:n:] and radix [:r:], it is guaranteed that
* [:n == int.parse(n.toRadixString(r), radix: r):].
* For any int `n` and valid radix `r`, it is guaranteed that
* `n == int.parse(n.toRadixString(r), radix: r)`.
*
* If the [source] is not a valid integer literal, optionally prefixed by a
* sign, the [onError] is called with the [source] as argument, and its return
* value is used instead. If no [onError] is provided, a [FormatException]
* is thrown.
* If the [source] does not contain a valid integer literal,
* optionally prefixed by a sign, a [FormatException] is thrown
* (unless the deprecated [onError] parameter is used, see below).
*
* The [onError] handler can be chosen to return `null`. This is preferable
* to to throwing and then immediately catching the [FormatException].
* Instead of throwing and immediately catching the [FormatException],
* instead use [tryParse] to handle a parsing error.
* Example:
*
* var value = int.parse(text, onError: (source) => null);
* if (value == null) ... handle the problem
*
* The [onError] function is only invoked if [source] is a [String]. It is
* not invoked if the [source] is, for example, `null`.
* ```dart
* var value = int.tryParse(text);
* if (value == null) ... handle the problem
* ```
*
* The [onError] parameter is deprecated and will be removed.
* Instead of `int.parse(string, onError: (string) { ... })`,
* Instead of `int.parse(string, onError: (string) => ...)`,
* you should use `int.tryParse(string) ?? (...)`.
*
* When source is not valid and [onError] is provided,
* whenever a [FormatException] would be thrown,
* [onError] is instead called with [source] as argument,
* and the result of that call is returned by [parse].
*/
external static int parse(String source,
{int radix, @deprecated int onError(String source)});