diff --git a/.github/labeler.yml b/.github/labeler.yml index 62e1bfae..c47b4254 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,5 +1,9 @@ # Configuration for .github/workflows/pull_request_label.yaml. -"package-args": +"package:args": - changed-files: - any-glob-to-any-file: 'pkgs/args/**' + +"package:fixnum": + - changed-files: + - any-glob-to-any-file: 'pkgs/fixnum/**' diff --git a/.github/workflows/fixnum.yaml b/.github/workflows/fixnum.yaml new file mode 100644 index 00000000..7ee55e50 --- /dev/null +++ b/.github/workflows/fixnum.yaml @@ -0,0 +1,73 @@ +name: package:fixnum + +on: + # Run CI on pushes to the main branch, and on PRs against main. + push: + branches: [ main ] + paths: + - '.github/workflows/fixnum.yaml' + - 'pkgs/fixnum/**' + pull_request: + branches: [ main ] + paths: + - '.github/workflows/fixnum.yaml' + - 'pkgs/fixnum/**' + schedule: + - cron: "0 0 * * 0" +env: + PUB_ENVIRONMENT: bot.github + +defaults: + run: + working-directory: pkgs/fixnum/ + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [3.1.0, dev] + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [3.1.0, dev] + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart test --platform vm + if: always() && steps.install.outcome == 'success' + - name: Run Chrome tests + run: dart test --platform chrome + if: always() && steps.install.outcome == 'success' diff --git a/README.md b/README.md index 1205554f..90972ffd 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This repository is home to various Dart packages under the [dart.dev](https://pu | Package | Description | Version | |---|---|---| | [args](pkgs/args/) | Library for defining parsers for parsing raw command-line arguments into a set of options and values. | [![pub package](https://img.shields.io/pub/v/args.svg)](https://pub.dev/packages/args) | +| [fixnum](pkgs/fixnum/) | Library for 32- and 64-bit signed fixed-width integers. | [![pub package](https://img.shields.io/pub/v/fixnum.svg)](https://pub.dev/packages/fixnum) | ## Publishing automation diff --git a/pkgs/fixnum/.gitignore b/pkgs/fixnum/.gitignore new file mode 100644 index 00000000..49ce72d7 --- /dev/null +++ b/pkgs/fixnum/.gitignore @@ -0,0 +1,3 @@ +.dart_tool/ +.packages +pubspec.lock diff --git a/pkgs/fixnum/AUTHORS b/pkgs/fixnum/AUTHORS new file mode 100644 index 00000000..3d772aa2 --- /dev/null +++ b/pkgs/fixnum/AUTHORS @@ -0,0 +1,9 @@ +# Names should be added to this file with this pattern: +# +# For individuals: +# Name +# +# For organizations: +# Organization +# +Google Inc. <*@google.com> \ No newline at end of file diff --git a/pkgs/fixnum/CHANGELOG.md b/pkgs/fixnum/CHANGELOG.md new file mode 100644 index 00000000..f3e5c019 --- /dev/null +++ b/pkgs/fixnum/CHANGELOG.md @@ -0,0 +1,69 @@ +## 1.1.1 + +* Require Dart `^3.1.0` +* Move to `dart-lang/core` monorepo. + +## 1.1.0 + +* Add `tryParseRadix`, `tryParseInt` and `tryParseHex` static methods + to both `Int32` and `Int64`. +* Document exception and overflow behavior of parse functions, + and of `toHexString`. +* Make `Int32` parse functions consistent with documentation (accept + leading minus sign, do not accept empty inputs). +* Update the minimum SDK constraint to 2.19. +* Update to package:lints 2.0.0. + +## 1.0.1 + +* Switch to using `package:lints`. +* Populate the pubspec `repository` field. + +## 1.0.0 + +* Stable null safety release. + +## 1.0.0-nullsafety.0 + +* Migrate to null safety. + * This is meant to be mostly non-breaking, for opted in users runtime errors + will be promoted to static errors. For non-opted in users the runtime + errors are still present in their original form. + +## 0.10.11 + +* Update minimum SDK constraint to version 2.1.1. + +## 0.10.10 + +* Fix `Int64` parsing to throw `FormatException` on an empty string or single + minus sign. Previous incorrect behaviour was to throw a `RangeError` or + silently return zero. + +## 0.10.9 + +* Add `Int64.toStringUnsigned()` and `Int64.toRadixStringUnsigned()` functions. + +## 0.10.8 + +* Set SDK version constraint to `>=2.0.0-dev.65 <3.0.0`. + +## 0.10.7 + +* Bug fix: Make bit shifts work at bitwidth boundaries. Previously, + `new Int64(3) << 64 == Int64(3)`. This ensures that the result is 0 in such + cases. +* Updated maximum SDK constraint from 2.0.0-dev.infinity to 2.0.0. + +## 0.10.6 + +* Fix `Int64([int value])` constructor to avoid rounding error on intermediate + results for large negative inputs when compiled to JavaScript. `new + Int64(-1000000000000000000)` used to produce the same value as + `Int64.parseInt("-1000000000000000001")` + +## 0.10.5 + +* Fix strong mode warning in overridden `compareTo()` methods. + +*No changelog entries for previous versions...* diff --git a/pkgs/fixnum/LICENSE b/pkgs/fixnum/LICENSE new file mode 100644 index 00000000..162572a4 --- /dev/null +++ b/pkgs/fixnum/LICENSE @@ -0,0 +1,27 @@ +Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkgs/fixnum/README.md b/pkgs/fixnum/README.md new file mode 100644 index 00000000..47aac7bb --- /dev/null +++ b/pkgs/fixnum/README.md @@ -0,0 +1,9 @@ +[![Dart CI](https://github.com/dart-lang/core/actions/workflows/fixnum.yaml/badge.svg)](https://github.com/dart-lang/core/actions/workflows/fixnum.yaml) +[![Pub](https://img.shields.io/pub/v/fixnum.svg)](https://pub.dev/packages/fixnum) +[![package publisher](https://img.shields.io/pub/publisher/fixnum.svg)](https://pub.dev/packages/fixnum/publisher) + +A fixed-width 32- and 64- bit integer library for Dart. + +Provides data types for signed 32- and 64-bit integers. +The integer implementations in this library are designed to work identically +whether executed on the Dart VM or compiled to JavaScript. diff --git a/pkgs/fixnum/analysis_options.yaml b/pkgs/fixnum/analysis_options.yaml new file mode 100644 index 00000000..50e529cd --- /dev/null +++ b/pkgs/fixnum/analysis_options.yaml @@ -0,0 +1,21 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + language: + strict-casts: true + +linter: + rules: + - avoid_private_typedef_functions + - avoid_unused_constructor_parameters + - cancel_subscriptions + - cascade_invocations + - join_return_with_assignment + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_runtimeType_toString + - package_api_docs + - prefer_const_declarations + - prefer_expression_function_bodies + - unnecessary_raw_strings + - use_string_buffers diff --git a/pkgs/fixnum/lib/fixnum.dart b/pkgs/fixnum/lib/fixnum.dart new file mode 100644 index 00000000..69916452 --- /dev/null +++ b/pkgs/fixnum/lib/fixnum.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Signed 32- and 64-bit integer support. +/// +/// The integer implementations in this library are designed to work +/// identically whether executed on the Dart VM or compiled to JavaScript. +library; + +export 'src/int32.dart'; +export 'src/int64.dart'; +export 'src/intx.dart'; diff --git a/pkgs/fixnum/lib/src/int32.dart b/pkgs/fixnum/lib/src/int32.dart new file mode 100644 index 00000000..8045bc1f --- /dev/null +++ b/pkgs/fixnum/lib/src/int32.dart @@ -0,0 +1,467 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// ignore_for_file: constant_identifier_names + +import 'int64.dart'; +import 'intx.dart'; +import 'utilities.dart' as u; + +/// An immutable 32-bit signed integer, in the range [-2^31, 2^31 - 1]. +/// Arithmetic operations may overflow in order to maintain this range. +class Int32 implements IntX { + /// The maximum positive value attainable by an [Int32], namely + /// 2147483647. + static const Int32 MAX_VALUE = Int32._internal(0x7FFFFFFF); + + /// The minimum positive value attainable by an [Int32], namely + /// -2147483648. + static const Int32 MIN_VALUE = Int32._internal(-0x80000000); + + /// An [Int32] constant equal to 0. + static const Int32 ZERO = Int32._internal(0); + + /// An [Int32] constant equal to 1. + static const Int32 ONE = Int32._internal(1); + + /// An [Int32] constant equal to 2. + static const Int32 TWO = Int32._internal(2); + + // Mask to 32-bits. + static const int _MASK_U32 = 0xFFFFFFFF; + + /// Parses [source] in a given [radix] between 2 and 36. + /// + /// Returns an [Int32] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 32 bit integer, + /// the numerical value is truncated to the lowest 32 bits + /// of the value's binary representation, + /// interpreted as a 32-bit two's complement integer. + /// + /// The [source] string must contain a sequence of base-[radix] + /// digits (using letters from `a` to `z` as digits with values 10 through + /// 25 for radixes above 10), possibly prefixed by a `-` sign. + /// + /// Throws a [FormatException] if the input is not a valid + /// integer numeral in base [radix]. + static Int32 parseRadix(String source, int radix) => + _parseRadix(source, u.validateRadix(radix), true)!; + + /// Parses [source] in a given [radix] between 2 and 36. + /// + /// Returns an [Int32] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 32 bit integer, + /// the numerical value is truncated to the lowest 32 bits + /// of the value's binary representation, + /// interpreted as a 32-bit two's complement integer. + /// + /// The [source] string must contain a sequence of base-[radix] + /// digits (using letters from `a` to `z` as digits with values 10 through + /// 25 for radixes above 10), possibly prefixed by a `-` sign. + /// + /// Throws a [FormatException] if the input is not a valid + /// integer numeral in base [radix]. + static Int32? tryParseRadix(String source, int radix) => + _parseRadix(source, u.validateRadix(radix), false); + + // TODO(rice) - Make this faster by converting several digits at once. + static Int32? _parseRadix(String s, int radix, bool throwOnError) { + var index = 0; + var negative = false; + if (s.startsWith('-')) { + negative = true; + index = 1; + } + if (index == s.length) { + if (!throwOnError) return null; + throw FormatException('No digits', s, index); + } + var result = 0; + for (; index < s.length; index++) { + var c = s.codeUnitAt(index); + var digit = u.decodeDigit(c); + if (digit < radix) { + /// Doesn't matter whether the result is unsigned + /// or signed (as on the web), only the bits matter + /// to the [Int32.new] constructor. + result = (result * radix + digit) & _MASK_U32; + } else { + if (!throwOnError) return null; + throw FormatException('Non radix code unit', s, index); + } + } + if (negative) result = -result; + return Int32(result); + } + + /// Parses [source] as a decimal numeral. + /// + /// Returns an [Int32] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 32 bit integer, + /// the numerical value is truncated to the lowest 32 bits + /// of the value's binary representation, + /// interpreted as a 32-bit two's complement integer. + /// + /// The [source] string must contain a sequence of digits (`0`-`9`), + /// possibly prefixed by a `-` sign. + /// + /// Throws a [FormatException] if the input is not a valid + /// decimal integer numeral. + static Int32 parseInt(String source) => _parseRadix(source, 10, true)!; + + /// Parses [source] as a decimal numeral. + /// + /// Returns an [Int32] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 32 bit integer, + /// the numerical value is truncated to the lowest 32 bits + /// of the value's binary representation, + /// interpreted as a 32-bit two's complement integer. + /// + /// The [source] string must contain a sequence of digits (`0`-`9`), + /// possibly prefixed by a `-` sign. + /// + /// Throws a [FormatException] if the input is not a valid + /// decimal integer numeral. + static Int32? tryParseInt(String source) => _parseRadix(source, 10, false); + + /// Parses [source] as a hexadecimal numeral. + /// + /// Returns an [Int32] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 32 bit integer, + /// the numerical value is truncated to the lowest 32 bits + /// of the value's binary representation, + /// interpreted as a 32-bit two's complement integer. + /// + /// The [source] string must contain a sequence of hexadecimal + /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign. + /// + /// Returns `null` if the input is not a valid + /// hexadecimal integer numeral. + static Int32 parseHex(String source) => _parseRadix(source, 16, true)!; + + /// Parses [source] as a hexadecimal numeral. + /// + /// Returns an [Int32] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 32 bit integer, + /// the numerical value is truncated to the lowest 32 bits + /// of the value's binary representation, + /// interpreted as a 32-bit two's complement integer. + /// + /// The [source] string must contain a sequence of hexadecimal + /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign. + /// + /// Returns `null` if the input is not a valid + /// hexadecimal integer numeral. + static Int32? tryParseHex(String source) => _parseRadix(source, 16, false); + + // The internal value, kept in the range [MIN_VALUE, MAX_VALUE]. + final int _i; + + const Int32._internal(int i) : _i = i; + + /// Constructs an [Int32] from an [int]. Only the low 32 bits of the input + /// are used. + Int32([int i = 0]) : _i = (i & 0x7fffffff) - (i & 0x80000000); + + // Returns the [int] representation of the specified value. Throws + // [ArgumentError] for non-integer arguments. + int _toInt(Object val) { + if (val is Int32) { + return val._i; + } else if (val is int) { + return val; + } + throw ArgumentError.value(val, 'other', 'Not an int, Int32 or Int64'); + } + + // The +, -, * , &, |, and ^ operaters deal with types as follows: + // + // Int32 + int => Int32 + // Int32 + Int32 => Int32 + // Int32 + Int64 => Int64 + // + // The %, ~/ and remainder operators return an Int32 even with an Int64 + // argument, since the result cannot be greater than the value on the + // left-hand side: + // + // Int32 % int => Int32 + // Int32 % Int32 => Int32 + // Int32 % Int64 => Int32 + + @override + IntX operator +(Object other) { + if (other is Int64) { + return toInt64() + other; + } + return Int32(_i + _toInt(other)); + } + + @override + IntX operator -(Object other) { + if (other is Int64) { + return toInt64() - other; + } + return Int32(_i - _toInt(other)); + } + + @override + Int32 operator -() => Int32(-_i); + + @override + IntX operator *(Object other) { + if (other is Int64) { + return toInt64() * other; + } + // TODO(rice) - optimize + return (toInt64() * other).toInt32(); + } + + @override + Int32 operator %(Object other) { + if (other is Int64) { + // Result will be Int32 + return (toInt64() % other).toInt32(); + } + return Int32(_i % _toInt(other)); + } + + @override + Int32 operator ~/(Object other) { + if (other is Int64) { + return (toInt64() ~/ other).toInt32(); + } + return Int32(_i ~/ _toInt(other)); + } + + @override + Int32 remainder(Object other) { + if (other is Int64) { + var t = toInt64(); + return (t - (t ~/ other) * other).toInt32(); + } + return this - (this ~/ other) * other as Int32; + } + + @override + Int32 operator &(Object other) { + if (other is Int64) { + return (toInt64() & other).toInt32(); + } + return Int32(_i & _toInt(other)); + } + + @override + Int32 operator |(Object other) { + if (other is Int64) { + return (toInt64() | other).toInt32(); + } + return Int32(_i | _toInt(other)); + } + + @override + Int32 operator ^(Object other) { + if (other is Int64) { + return (toInt64() ^ other).toInt32(); + } + return Int32(_i ^ _toInt(other)); + } + + @override + Int32 operator ~() => Int32(~_i); + + @override + Int32 operator <<(int n) { + if (n < 0) { + throw ArgumentError(n); + } + if (n >= 32) { + return ZERO; + } + return Int32(_i << n); + } + + @override + Int32 operator >>(int n) { + if (n < 0) { + throw ArgumentError(n); + } + if (n >= 32) { + return isNegative ? const Int32._internal(-1) : ZERO; + } + int value; + if (_i >= 0) { + value = _i >> n; + } else { + value = (_i >> n) | (0xffffffff << (32 - n)); + } + return Int32(value); + } + + @override + Int32 shiftRightUnsigned(int n) { + if (n < 0) { + throw ArgumentError(n); + } + if (n >= 32) { + return ZERO; + } + int value; + if (_i >= 0) { + value = _i >> n; + } else { + value = (_i >> n) & ((1 << (32 - n)) - 1); + } + return Int32(value); + } + + /// Returns [:true:] if this [Int32] has the same numeric value as the + /// given object. The argument may be an [int] or an [IntX]. + @override + bool operator ==(Object other) { + if (other is Int32) { + return _i == other._i; + } else if (other is Int64) { + return toInt64() == other; + } else if (other is int) { + return _i == other; + } + return false; + } + + @override + int compareTo(Object other) { + if (other is Int64) { + return toInt64().compareTo(other); + } + return _i.compareTo(_toInt(other)); + } + + @override + bool operator <(Object other) { + if (other is Int64) { + return toInt64() < other; + } + return _i < _toInt(other); + } + + @override + bool operator <=(Object other) { + if (other is Int64) { + return toInt64() <= other; + } + return _i <= _toInt(other); + } + + @override + bool operator >(Object other) { + if (other is Int64) { + return toInt64() > other; + } + return _i > _toInt(other); + } + + @override + bool operator >=(Object other) { + if (other is Int64) { + return toInt64() >= other; + } + return _i >= _toInt(other); + } + + @override + bool get isEven => (_i & 0x1) == 0; + + @override + bool get isMaxValue => _i == 2147483647; + + @override + bool get isMinValue => _i == -2147483648; + + @override + bool get isNegative => _i < 0; + + @override + bool get isOdd => (_i & 0x1) == 1; + + @override + bool get isZero => _i == 0; + + @override + int get bitLength => _i.bitLength; + + @override + int get hashCode => _i; + + @override + Int32 abs() => _i < 0 ? Int32(-_i) : this; + + @override + Int32 clamp(Object lowerLimit, Object upperLimit) { + if (this < lowerLimit) { + if (lowerLimit is IntX) return lowerLimit.toInt32(); + if (lowerLimit is int) return Int32(lowerLimit); + throw ArgumentError(lowerLimit); + } else if (this > upperLimit) { + if (upperLimit is IntX) return upperLimit.toInt32(); + if (upperLimit is int) return Int32(upperLimit); + throw ArgumentError(upperLimit); + } + return this; + } + + @override + int numberOfLeadingZeros() => u.numberOfLeadingZeros(_i); + + @override + int numberOfTrailingZeros() => u.numberOfTrailingZeros(_i); + + @override + Int32 toSigned(int width) { + if (width < 1 || width > 32) throw RangeError.range(width, 1, 32); + return Int32(_i.toSigned(width)); + } + + @override + Int32 toUnsigned(int width) { + if (width < 0 || width > 32) throw RangeError.range(width, 0, 32); + return Int32(_i.toUnsigned(width)); + } + + @override + List toBytes() { + var result = List.filled(4, 0); + result[0] = _i & 0xff; + result[1] = (_i >> 8) & 0xff; + result[2] = (_i >> 16) & 0xff; + result[3] = (_i >> 24) & 0xff; + return result; + } + + @override + double toDouble() => _i.toDouble(); + + @override + int toInt() => _i; + + @override + Int32 toInt32() => this; + + @override + Int64 toInt64() => Int64(_i); + + @override + String toString() => _i.toString(); + + @override + String toHexString() => _i.toRadixString(16); + + @override + String toRadixString(int radix) => _i.toRadixString(radix); +} diff --git a/pkgs/fixnum/lib/src/int64.dart b/pkgs/fixnum/lib/src/int64.dart new file mode 100644 index 00000000..bbfcbd91 --- /dev/null +++ b/pkgs/fixnum/lib/src/int64.dart @@ -0,0 +1,1128 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// ignore_for_file: constant_identifier_names + +// Many locals are declared as `int` or `double`. We keep local variable types +// because the types are critical to the efficiency of many operations. +// +// ignore_for_file: omit_local_variable_types + +import 'int32.dart'; +import 'intx.dart'; +import 'utilities.dart' as u; + +/// An immutable 64-bit signed integer, in the range [-2^63, 2^63 - 1]. +/// Arithmetic operations may overflow in order to maintain this range. +class Int64 implements IntX { + // A 64-bit integer is represented internally as three non-negative + // integers, storing the 22 low, 22 middle, and 20 high bits of the + // 64-bit value. _l (low) and _m (middle) are in the range + // [0, 2^22 - 1] and _h (high) is in the range [0, 2^20 - 1]. + // + // The values being assigned to _l, _m and _h in initialization are masked to + // force them into the above ranges. Sometimes we know that the value is a + // small non-negative integer but the dart2js compiler can't infer that, so a + // few of the masking operations are not needed for correctness but are + // helpful for dart2js code quality. + + final int _l, _m, _h; + + // Note: several functions require _BITS == 22 -- do not change this value. + static const int _BITS = 22; + static const int _BITS01 = 44; // 2 * _BITS + static const int _BITS2 = 20; // 64 - _BITS01 + static const int _MASK = 4194303; // (1 << _BITS) - 1 + static const int _MASK2 = 1048575; // (1 << _BITS2) - 1 + static const int _SIGN_BIT = 19; // _BITS2 - 1 + static const int _SIGN_BIT_MASK = 1 << _SIGN_BIT; + + /// The maximum positive value attainable by an [Int64], namely + /// 9,223,372,036,854,775,807. + static const Int64 MAX_VALUE = Int64._bits(_MASK, _MASK, _MASK2 >> 1); + + /// The minimum positive value attainable by an [Int64], namely + /// -9,223,372,036,854,775,808. + static const Int64 MIN_VALUE = Int64._bits(0, 0, _SIGN_BIT_MASK); + + /// An [Int64] constant equal to 0. + static const Int64 ZERO = Int64._bits(0, 0, 0); + + /// An [Int64] constant equal to 1. + static const Int64 ONE = Int64._bits(1, 0, 0); + + /// An [Int64] constant equal to 2. + static const Int64 TWO = Int64._bits(2, 0, 0); + + /// Constructs an [Int64] with a given bitwise representation. No validation + /// is performed. + const Int64._bits(this._l, this._m, this._h); + + /// Parses [source] in a given [radix] between 2 and 36. + /// + /// Returns an [Int64] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 64 bit integer, + /// the numerical value is truncated to the lowest 64 bits + /// of the value's binary representation, + /// interpreted as a 64-bit two's complement integer. + /// + /// The [source] string must contain a sequence of base-[radix] + /// digits (using letters from `a` to `z` as digits with values 10 through + /// 25 for radixes above 10), possibly prefixed by a `-` sign. + /// + /// Throws a [FormatException] if the input is not recognized as a valid + /// integer numeral. + static Int64 parseRadix(String source, int radix) => + _parseRadix(source, u.validateRadix(radix), true)!; + + /// Parses [source] in a given [radix] between 2 and 36. + /// + /// Returns an [Int64] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 64 bit integer, + /// the numerical value is truncated to the lowest 64 bits + /// of the value's binary representation, + /// interpreted as a 64-bit two's complement integer. + /// + /// The [source] string must contain a sequence of base-[radix] + /// digits (using letters from `a` to `z` as digits with values 10 through + /// 25 for radixes above 10), possibly prefixed by a `-` sign. + /// + /// Returns `null` if the input is not recognized as a valid + /// integer numeral. + static Int64? tryParseRadix(String source, int radix) => + _parseRadix(source, u.validateRadix(radix), false); + + static Int64? _parseRadix(String s, int radix, bool throwOnError) { + int i = 0; + bool negative = false; + if (s.startsWith('-')) { + negative = true; + i++; + } + + if (i >= s.length) { + if (!throwOnError) return null; + throw FormatException('No digits', s, i); + } + + int d0 = 0, d1 = 0, d2 = 0; // low, middle, high components. + for (; i < s.length; i++) { + int c = s.codeUnitAt(i); + int digit = u.decodeDigit(c); + if (digit < radix) { + // [radix] and [digit] are at most 6 bits, component is 22, so we can + // multiply and add within 30 bit temporary values. + d0 = d0 * radix + digit; + int carry = d0 >> _BITS; + d0 = _MASK & d0; + + d1 = d1 * radix + carry; + carry = d1 >> _BITS; + d1 = _MASK & d1; + + d2 = d2 * radix + carry; + d2 = _MASK2 & d2; + } else { + if (!throwOnError) return null; + throw FormatException('Not radix digit', s, i); + } + } + + if (negative) return _negate(d0, d1, d2); + + return Int64._masked(d0, d1, d2); + } + + /// Parses [source] as a decimal numeral. + /// + /// Returns an [Int64] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 64 bit integer, + /// the numerical value is truncated to the lowest 64 bits + /// of the value's binary representation, + /// interpreted as a 64-bit two's complement integer. + /// + /// The [source] string must contain a sequence of digits (`0`-`9`), + /// possibly prefixed by a `-` sign. + /// + /// Throws a [FormatException] if the input is not a valid + /// decimal integer numeral. + static Int64 parseInt(String source) => _parseRadix(source, 10, true)!; + + /// Parses [source] as a decimal numeral. + /// + /// Returns an [Int64] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 64 bit integer, + /// the numerical value is truncated to the lowest 64 bits + /// of the value's binary representation, + /// interpreted as a 64-bit two's complement integer. + /// + /// The [source] string must contain a sequence of digits (`0`-`9`), + /// possibly prefixed by a `-` sign. + /// + /// Returns `null` if the input is not a valid + /// decimal integer numeral. + static Int64? tryParseInt(String source) => _parseRadix(source, 10, false); + + /// Parses [source] as a hexadecimal numeral. + /// + /// Returns an [Int64] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 64 bit integer, + /// the numerical value is truncated to the lowest 64 bits + /// of the value's binary representation, + /// interpreted as a 64-bit two's complement integer. + /// + /// The [source] string must contain a sequence of hexadecimal + /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign. + /// + /// Throws a [FormatException] if the input is not a valid + /// hexadecimal integer numeral. + static Int64 parseHex(String source) => _parseRadix(source, 16, true)!; + + /// Parses [source] as a hexadecimal numeral. + /// + /// Returns an [Int64] with the numerical value of [source]. + /// If the numerical value of [source] does not fit + /// in a signed 64 bit integer, + /// the numerical value is truncated to the lowest 64 bits + /// of the value's binary representation, + /// interpreted as a 64-bit two's complement integer. + /// + /// The [source] string must contain a sequence of hexadecimal + /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign. + /// + /// Returns `null` if the input is not a valid + /// hexadecimal integer numeral. + static Int64? tryParseHex(String source) => _parseRadix(source, 16, false); + + // + // Public constructors + // + + /// Constructs an [Int64] with a given [int] value; zero by default. + factory Int64([int value = 0]) { + int v0 = 0, v1 = 0, v2 = 0; + bool negative = false; + if (value < 0) { + negative = true; + value = -value; + } + // Avoid using bitwise operations that in JavaScript coerce their input to + // 32 bits. + v2 = value ~/ 17592186044416; // 2^44 + value -= v2 * 17592186044416; + v1 = value ~/ 4194304; // 2^22 + value -= v1 * 4194304; + v0 = value; + + return negative + ? Int64._negate(_MASK & v0, _MASK & v1, _MASK2 & v2) + : Int64._masked(v0, v1, v2); + } + + factory Int64.fromBytes(List bytes) { + // 20 bits into top, 22 into middle and bottom. + var split1 = bytes[5] & 0xFF; + var high = + ((bytes[7] & 0xFF) << 12) | ((bytes[6] & 0xFF) << 4) | (split1 >> 4); + var split2 = bytes[2] & 0xFF; + var middle = (split1 << 18) | + ((bytes[4] & 0xFF) << 10) | + ((bytes[3] & 0xFF) << 2) | + (split2 >> 6); + var low = (split2 << 16) | ((bytes[1] & 0xFF) << 8) | (bytes[0] & 0xFF); + // Top bits from above will be masked off here. + return Int64._masked(low, middle, high); + } + + factory Int64.fromBytesBigEndian(List bytes) { + var split1 = bytes[2] & 0xFF; + var high = + ((bytes[0] & 0xFF) << 12) | ((bytes[1] & 0xFF) << 4) | (split1 >> 4); + var split2 = bytes[5] & 0xFF; + var middle = (split1 << 18) | + ((bytes[3] & 0xFF) << 10) | + ((bytes[4] & 0xFF) << 2) | + (split2 >> 6); + var low = (split2 << 16) | ((bytes[6] & 0xFF) << 8) | (bytes[7] & 0xFF); + // Top bits from above will be masked off here. + return Int64._masked(low, middle, high); + } + + /// Constructs an [Int64] from a pair of 32-bit integers having the value + /// [:((top & 0xffffffff) << 32) | (bottom & 0xffffffff):]. + factory Int64.fromInts(int top, int bottom) { + top &= 0xffffffff; + bottom &= 0xffffffff; + int d0 = _MASK & bottom; + int d1 = ((0xfff & top) << 10) | (0x3ff & (bottom >> _BITS)); + int d2 = _MASK2 & (top >> 12); + return Int64._masked(d0, d1, d2); + } + + // Returns the [Int64] representation of the specified value. Throws + // [ArgumentError] for non-integer arguments. + static Int64 _promote(Object value) { + if (value is Int64) { + return value; + } else if (value is int) { + return Int64(value); + } else if (value is Int32) { + return value.toInt64(); + } + throw ArgumentError.value(value, 'other', 'not an int, Int32 or Int64'); + } + + @override + Int64 operator +(Object other) { + Int64 o = _promote(other); + int sum0 = _l + o._l; + int sum1 = _m + o._m + (sum0 >> _BITS); + int sum2 = _h + o._h + (sum1 >> _BITS); + return Int64._masked(sum0, sum1, sum2); + } + + @override + Int64 operator -(Object other) { + Int64 o = _promote(other); + return _sub(_l, _m, _h, o._l, o._m, o._h); + } + + @override + Int64 operator -() => _negate(_l, _m, _h); + + @override + Int64 operator *(Object other) { + Int64 o = _promote(other); + + // Grab 13-bit chunks. + int a0 = _l & 0x1fff; + int a1 = (_l >> 13) | ((_m & 0xf) << 9); + int a2 = (_m >> 4) & 0x1fff; + int a3 = (_m >> 17) | ((_h & 0xff) << 5); + int a4 = (_h & 0xfff00) >> 8; + + int b0 = o._l & 0x1fff; + int b1 = (o._l >> 13) | ((o._m & 0xf) << 9); + int b2 = (o._m >> 4) & 0x1fff; + int b3 = (o._m >> 17) | ((o._h & 0xff) << 5); + int b4 = (o._h & 0xfff00) >> 8; + + // Compute partial products. + // Optimization: if b is small, avoid multiplying by parts that are 0. + int p0 = a0 * b0; // << 0 + int p1 = a1 * b0; // << 13 + int p2 = a2 * b0; // << 26 + int p3 = a3 * b0; // << 39 + int p4 = a4 * b0; // << 52 + + if (b1 != 0) { + p1 += a0 * b1; + p2 += a1 * b1; + p3 += a2 * b1; + p4 += a3 * b1; + } + if (b2 != 0) { + p2 += a0 * b2; + p3 += a1 * b2; + p4 += a2 * b2; + } + if (b3 != 0) { + p3 += a0 * b3; + p4 += a1 * b3; + } + if (b4 != 0) { + p4 += a0 * b4; + } + + // Accumulate into 22-bit chunks: + // .........................................c10|...................c00| + // |....................|..................xxxx|xxxxxxxxxxxxxxxxxxxxxx| p0 + // |....................|......................|......................| + // |....................|...................c11|......c01.............| + // |....................|....xxxxxxxxxxxxxxxxxx|xxxxxxxxx.............| p1 + // |....................|......................|......................| + // |.................c22|...............c12....|......................| + // |..........xxxxxxxxxx|xxxxxxxxxxxxxxxxxx....|......................| p2 + // |....................|......................|......................| + // |.................c23|..c13.................|......................| + // |xxxxxxxxxxxxxxxxxxxx|xxxxx.................|......................| p3 + // |....................|......................|......................| + // |.........c24........|......................|......................| + // |xxxxxxxxxxxx........|......................|......................| p4 + + int c00 = p0 & 0x3fffff; + int c01 = (p1 & 0x1ff) << 13; + int c0 = c00 + c01; + + int c10 = p0 >> 22; + int c11 = p1 >> 9; + int c12 = (p2 & 0x3ffff) << 4; + int c13 = (p3 & 0x1f) << 17; + int c1 = c10 + c11 + c12 + c13; + + int c22 = p2 >> 18; + int c23 = p3 >> 5; + int c24 = (p4 & 0xfff) << 8; + int c2 = c22 + c23 + c24; + + // Propagate high bits from c0 -> c1, c1 -> c2. + c1 += c0 >> _BITS; + c2 += c1 >> _BITS; + + return Int64._masked(c0, c1, c2); + } + + @override + Int64 operator %(Object other) => _divide(this, other, _RETURN_MOD); + + @override + Int64 operator ~/(Object other) => _divide(this, other, _RETURN_DIV); + + @override + Int64 remainder(Object other) => _divide(this, other, _RETURN_REM); + + @override + Int64 operator &(Object other) { + Int64 o = _promote(other); + int a0 = _l & o._l; + int a1 = _m & o._m; + int a2 = _h & o._h; + return Int64._masked(a0, a1, a2); + } + + @override + Int64 operator |(Object other) { + Int64 o = _promote(other); + int a0 = _l | o._l; + int a1 = _m | o._m; + int a2 = _h | o._h; + return Int64._masked(a0, a1, a2); + } + + @override + Int64 operator ^(Object other) { + Int64 o = _promote(other); + int a0 = _l ^ o._l; + int a1 = _m ^ o._m; + int a2 = _h ^ o._h; + return Int64._masked(a0, a1, a2); + } + + @override + Int64 operator ~() => Int64._masked(~_l, ~_m, ~_h); + + @override + Int64 operator <<(int n) { + if (n < 0) { + throw ArgumentError.value(n); + } + if (n >= 64) { + return ZERO; + } + + int res0, res1, res2; + if (n < _BITS) { + res0 = _l << n; + res1 = (_m << n) | (_l >> (_BITS - n)); + res2 = (_h << n) | (_m >> (_BITS - n)); + } else if (n < _BITS01) { + res0 = 0; + res1 = _l << (n - _BITS); + res2 = (_m << (n - _BITS)) | (_l >> (_BITS01 - n)); + } else { + res0 = 0; + res1 = 0; + res2 = _l << (n - _BITS01); + } + + return Int64._masked(res0, res1, res2); + } + + @override + Int64 operator >>(int n) { + if (n < 0) { + throw ArgumentError.value(n); + } + if (n >= 64) { + return isNegative ? const Int64._bits(_MASK, _MASK, _MASK2) : ZERO; + } + + int res0, res1, res2; + + // Sign extend h(a). + int a2 = _h; + bool negative = (a2 & _SIGN_BIT_MASK) != 0; + if (negative && _MASK > _MASK2) { + // Add extra one bits on the left so the sign gets shifted into the wider + // lower words. + a2 += _MASK - _MASK2; + } + + if (n < _BITS) { + res2 = _shiftRight(a2, n); + if (negative) { + res2 |= _MASK2 & ~(_MASK2 >> n); + } + res1 = _shiftRight(_m, n) | (a2 << (_BITS - n)); + res0 = _shiftRight(_l, n) | (_m << (_BITS - n)); + } else if (n < _BITS01) { + res2 = negative ? _MASK2 : 0; + res1 = _shiftRight(a2, n - _BITS); + if (negative) { + res1 |= _MASK & ~(_MASK >> (n - _BITS)); + } + res0 = _shiftRight(_m, n - _BITS) | (a2 << (_BITS01 - n)); + } else { + res2 = negative ? _MASK2 : 0; + res1 = negative ? _MASK : 0; + res0 = _shiftRight(a2, n - _BITS01); + if (negative) { + res0 |= _MASK & ~(_MASK >> (n - _BITS01)); + } + } + + return Int64._masked(res0, res1, res2); + } + + @override + Int64 shiftRightUnsigned(int n) { + if (n < 0) { + throw ArgumentError.value(n); + } + if (n >= 64) { + return ZERO; + } + + int res0, res1, res2; + int a2 = _MASK2 & _h; // Ensure a2 is positive. + if (n < _BITS) { + res2 = a2 >> n; + res1 = (_m >> n) | (a2 << (_BITS - n)); + res0 = (_l >> n) | (_m << (_BITS - n)); + } else if (n < _BITS01) { + res2 = 0; + res1 = a2 >> (n - _BITS); + res0 = (_m >> (n - _BITS)) | (_h << (_BITS01 - n)); + } else { + res2 = 0; + res1 = 0; + res0 = a2 >> (n - _BITS01); + } + + return Int64._masked(res0, res1, res2); + } + + /// Returns [:true:] if this [Int64] has the same numeric value as the + /// given object. The argument may be an [int] or an [IntX]. + @override + bool operator ==(Object other) { + Int64? o; + if (other is Int64) { + o = other; + } else if (other is int) { + if (_h == 0 && _m == 0) return _l == other; + // Since we know one of [_h] or [_m] is non-zero, if [other] fits in the + // low word then it can't be numerically equal. + if ((_MASK & other) == other) return false; + o = Int64(other); + } else if (other is Int32) { + o = other.toInt64(); + } + if (o != null) { + return _l == o._l && _m == o._m && _h == o._h; + } + return false; + } + + @override + int compareTo(Object other) => _compareTo(other); + + int _compareTo(Object other) { + Int64 o = _promote(other); + int signa = _h >> (_BITS2 - 1); + int signb = o._h >> (_BITS2 - 1); + if (signa != signb) { + return signa == 0 ? 1 : -1; + } + if (_h > o._h) { + return 1; + } else if (_h < o._h) { + return -1; + } + if (_m > o._m) { + return 1; + } else if (_m < o._m) { + return -1; + } + if (_l > o._l) { + return 1; + } else if (_l < o._l) { + return -1; + } + return 0; + } + + @override + bool operator <(Object other) => _compareTo(other) < 0; + + @override + bool operator <=(Object other) => _compareTo(other) <= 0; + + @override + bool operator >(Object other) => _compareTo(other) > 0; + + @override + bool operator >=(Object other) => _compareTo(other) >= 0; + + @override + bool get isEven => (_l & 0x1) == 0; + + @override + bool get isMaxValue => (_h == _MASK2 >> 1) && _m == _MASK && _l == _MASK; + + @override + bool get isMinValue => _h == _SIGN_BIT_MASK && _m == 0 && _l == 0; + + @override + bool get isNegative => (_h & _SIGN_BIT_MASK) != 0; + + @override + bool get isOdd => (_l & 0x1) == 1; + + @override + bool get isZero => _h == 0 && _m == 0 && _l == 0; + + @override + int get bitLength { + if (isZero) return 0; + int a0 = _l, a1 = _m, a2 = _h; + if (isNegative) { + a0 = _MASK & ~a0; + a1 = _MASK & ~a1; + a2 = _MASK2 & ~a2; + } + if (a2 != 0) return _BITS01 + a2.bitLength; + if (a1 != 0) return _BITS + a1.bitLength; + return a0.bitLength; + } + + /// Returns a hash code based on all the bits of this [Int64]. + @override + int get hashCode { + // TODO(sra): Should we ensure that hashCode values match corresponding int? + // i.e. should `new Int64(x).hashCode == x.hashCode`? + int bottom = ((_m & 0x3ff) << _BITS) | _l; + int top = (_h << 12) | ((_m >> 10) & 0xfff); + return bottom ^ top; + } + + @override + Int64 abs() => isNegative ? -this : this; + + @override + Int64 clamp(Object lowerLimit, Object upperLimit) { + Int64 lower = _promote(lowerLimit); + Int64 upper = _promote(upperLimit); + if (this < lower) return lower; + if (this > upper) return upper; + return this; + } + + /// Returns the number of leading zeros in this [Int64] as an [int] + /// between 0 and 64. + @override + int numberOfLeadingZeros() { + int b2 = u.numberOfLeadingZeros(_h); + if (b2 == 32) { + int b1 = u.numberOfLeadingZeros(_m); + if (b1 == 32) { + return u.numberOfLeadingZeros(_l) + 32; + } else { + return b1 + _BITS2 - (32 - _BITS); + } + } else { + return b2 - (32 - _BITS2); + } + } + + /// Returns the number of trailing zeros in this [Int64] as an [int] + /// between 0 and 64. + @override + int numberOfTrailingZeros() { + int zeros = u.numberOfTrailingZeros(_l); + if (zeros < 32) { + return zeros; + } + + zeros = u.numberOfTrailingZeros(_m); + if (zeros < 32) { + return _BITS + zeros; + } + + zeros = u.numberOfTrailingZeros(_h); + if (zeros < 32) { + return _BITS01 + zeros; + } + // All zeros + return 64; + } + + @override + Int64 toSigned(int width) { + if (width < 1 || width > 64) throw RangeError.range(width, 1, 64); + if (width > _BITS01) { + return Int64._masked(_l, _m, _h.toSigned(width - _BITS01)); + } else if (width > _BITS) { + int m = _m.toSigned(width - _BITS); + return m.isNegative + ? Int64._masked(_l, m, _MASK2) + : Int64._masked(_l, m, 0); // Masking for type inferrer. + } else { + int l = _l.toSigned(width); + return l.isNegative + ? Int64._masked(l, _MASK, _MASK2) + : Int64._masked(l, 0, 0); // Masking for type inferrer. + } + } + + @override + Int64 toUnsigned(int width) { + if (width < 0 || width > 64) throw RangeError.range(width, 0, 64); + if (width > _BITS01) { + int h = _h.toUnsigned(width - _BITS01); + return Int64._masked(_l, _m, h); + } else if (width > _BITS) { + int m = _m.toUnsigned(width - _BITS); + return Int64._masked(_l, m, 0); + } else { + int l = _l.toUnsigned(width); + return Int64._masked(l, 0, 0); + } + } + + @override + List toBytes() { + var result = List.filled(8, 0); + result[0] = _l & 0xff; + result[1] = (_l >> 8) & 0xff; + result[2] = ((_m << 6) & 0xfc) | ((_l >> 16) & 0x3f); + result[3] = (_m >> 2) & 0xff; + result[4] = (_m >> 10) & 0xff; + result[5] = ((_h << 4) & 0xf0) | ((_m >> 18) & 0xf); + result[6] = (_h >> 4) & 0xff; + result[7] = (_h >> 12) & 0xff; + return result; + } + + @override + double toDouble() => toInt().toDouble(); + + @override + int toInt() { + int l = _l; + int m = _m; + int h = _h; + // In the sum we add least significant to most significant so that in + // JavaScript double arithmetic rounding occurs on only the last addition. + if ((_h & _SIGN_BIT_MASK) != 0) { + l = _MASK & ~_l; + m = _MASK & ~_m; + h = _MASK2 & ~_h; + return -((1 + l) + (4194304 * m) + (17592186044416 * h)); + } else { + return l + (4194304 * m) + (17592186044416 * h); + } + } + + /// Returns an [Int32] containing the low 32 bits of this [Int64]. + @override + Int32 toInt32() => Int32(((_m & 0x3ff) << _BITS) | _l); + + /// Returns `this`. + @override + Int64 toInt64() => this; + + /// Returns the value of this [Int64] as a decimal [String]. + @override + String toString() => _toRadixString(10); + + @override + String toHexString() { + if (isZero) return '0'; + Int64 x = this; + String hexStr = ''; + while (!x.isZero) { + int digit = x._l & 0xf; + hexStr = '${_hexDigit(digit)}$hexStr'; + x = x.shiftRightUnsigned(4); + } + return hexStr; + } + + /// Returns the digits of `this` when interpreted as an unsigned 64-bit value. + @pragma('dart2js:noInline') + String toStringUnsigned() => _toRadixStringUnsigned(10, _l, _m, _h, ''); + + @pragma('dart2js:noInline') + String toRadixStringUnsigned(int radix) => + _toRadixStringUnsigned(u.validateRadix(radix), _l, _m, _h, ''); + + @override + String toRadixString(int radix) => _toRadixString(u.validateRadix(radix)); + + String _toRadixString(int radix) { + int d0 = _l; + int d1 = _m; + int d2 = _h; + + String sign = ''; + if ((d2 & _SIGN_BIT_MASK) != 0) { + sign = '-'; + + // Negate in-place. + d0 = 0 - d0; + int borrow = (d0 >> _BITS) & 1; + d0 &= _MASK; + d1 = 0 - d1 - borrow; + borrow = (d1 >> _BITS) & 1; + d1 &= _MASK; + d2 = 0 - d2 - borrow; + d2 &= _MASK2; + // d2, d1, d0 now are an unsigned 64 bit integer for MIN_VALUE and an + // unsigned 63 bit integer for other values. + } + return _toRadixStringUnsigned(radix, d0, d1, d2, sign); + } + + static String _toRadixStringUnsigned( + int radix, int d0, int d1, int d2, String sign) { + if (d0 == 0 && d1 == 0 && d2 == 0) return '0'; + + // Rearrange components into five components where all but the most + // significant are 10 bits wide. + // + // d4, d3, d4, d1, d0: 24 + 10 + 10 + 10 + 10 bits + // + // The choice of 10 bits allows a remainder of 20 bits to be scaled by 10 + // bits and added during division while keeping all intermediate values + // within 30 bits (unsigned small integer range for 32 bit implementations + // of Dart VM and V8). + // + // 6 6 5 4 3 2 1 + // 3210987654321098765432109876543210987654321098765432109876543210 + // [--------d2--------][---------d1---------][---------d0---------] + // --> + // [----------d4----------][---d3---][---d2---][---d1---][---d0---] + + int d4 = (d2 << 4) | (d1 >> 18); + int d3 = (d1 >> 8) & 0x3ff; + d2 = ((d1 << 2) | (d0 >> 20)) & 0x3ff; + d1 = (d0 >> 10) & 0x3ff; + d0 = d0 & 0x3ff; + + int fatRadix = _fatRadixTable[radix]; + + // Generate chunks of digits. In radix 10, generate 6 digits per chunk. + // + // This loop generates at most 3 chunks, so we store the chunks in locals + // rather than a list. We are trying to generate digits 20 bits at a time + // until we have only 30 bits left. 20 + 20 + 30 > 64 would imply that we + // need only two chunks, but radix values 17-19 and 33-36 generate only 15 + // or 16 bits per iteration, so sometimes the third chunk is needed. + + String chunk1 = '', chunk2 = '', chunk3 = ''; + + while (!(d4 == 0 && d3 == 0)) { + int q = d4 ~/ fatRadix; + int r = d4 - q * fatRadix; + d4 = q; + d3 += r << 10; + + q = d3 ~/ fatRadix; + r = d3 - q * fatRadix; + d3 = q; + d2 += r << 10; + + q = d2 ~/ fatRadix; + r = d2 - q * fatRadix; + d2 = q; + d1 += r << 10; + + q = d1 ~/ fatRadix; + r = d1 - q * fatRadix; + d1 = q; + d0 += r << 10; + + q = d0 ~/ fatRadix; + r = d0 - q * fatRadix; + d0 = q; + + assert(chunk3 == ''); + chunk3 = chunk2; + chunk2 = chunk1; + // Adding [fatRadix] Forces an extra digit which we discard to get a fixed + // width. E.g. (1000000 + 123) -> "1000123" -> "000123". An alternative + // would be to pad to the left with zeroes. + chunk1 = (fatRadix + r).toRadixString(radix).substring(1); + } + int residue = (d2 << 20) + (d1 << 10) + d0; + String leadingDigits = residue == 0 ? '' : residue.toRadixString(radix); + return '$sign$leadingDigits$chunk1$chunk2$chunk3'; + } + + // Table of 'fat' radix values. Each entry for index `i` is the largest power + // of `i` whose remainder fits in 20 bits. + static const _fatRadixTable = [ + 0, + 0, + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2 * + 2, + 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3, + 4 * 4 * 4 * 4 * 4 * 4 * 4 * 4 * 4 * 4, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 6 * 6 * 6 * 6 * 6 * 6 * 6, + 7 * 7 * 7 * 7 * 7 * 7 * 7, + 8 * 8 * 8 * 8 * 8 * 8, + 9 * 9 * 9 * 9 * 9 * 9, + 10 * 10 * 10 * 10 * 10 * 10, + 11 * 11 * 11 * 11 * 11, + 12 * 12 * 12 * 12 * 12, + 13 * 13 * 13 * 13 * 13, + 14 * 14 * 14 * 14 * 14, + 15 * 15 * 15 * 15 * 15, + 16 * 16 * 16 * 16 * 16, + 17 * 17 * 17 * 17, + 18 * 18 * 18 * 18, + 19 * 19 * 19 * 19, + 20 * 20 * 20 * 20, + 21 * 21 * 21 * 21, + 22 * 22 * 22 * 22, + 23 * 23 * 23 * 23, + 24 * 24 * 24 * 24, + 25 * 25 * 25 * 25, + 26 * 26 * 26 * 26, + 27 * 27 * 27 * 27, + 28 * 28 * 28 * 28, + 29 * 29 * 29 * 29, + 30 * 30 * 30 * 30, + 31 * 31 * 31 * 31, + 32 * 32 * 32 * 32, + 33 * 33 * 33, + 34 * 34 * 34, + 35 * 35 * 35, + 36 * 36 * 36 + ]; + + String toDebugString() => 'Int64[_l=$_l, _m=$_m, _h=$_h]'; + + static Int64 _masked(int low, int medium, int high) => + Int64._bits(_MASK & low, _MASK & medium, _MASK2 & high); + + static Int64 _sub(int a0, int a1, int a2, int b0, int b1, int b2) { + int diff0 = a0 - b0; + int diff1 = a1 - b1 - ((diff0 >> _BITS) & 1); + int diff2 = a2 - b2 - ((diff1 >> _BITS) & 1); + return _masked(diff0, diff1, diff2); + } + + static Int64 _negate(int b0, int b1, int b2) => _sub(0, 0, 0, b0, b1, b2); + + String _hexDigit(int digit) => '0123456789ABCDEF'[digit]; + + // Work around dart2js bugs with negative arguments to '>>' operator. + static int _shiftRight(int x, int n) { + if (x >= 0) { + return x >> n; + } else { + int shifted = x >> n; + if (shifted >= 0x80000000) { + shifted -= 4294967296; + } + return shifted; + } + } + + // Implementation of '~/', '%' and 'remainder'. + + static Int64 _divide(Int64 a, Object other, int what) { + Int64 b = _promote(other); + if (b.isZero) { + throw UnsupportedError('Division by zero'); + } + if (a.isZero) return ZERO; + + bool aNeg = a.isNegative; + bool bNeg = b.isNegative; + a = a.abs(); + b = b.abs(); + + int a0 = a._l; + int a1 = a._m; + int a2 = a._h; + + int b0 = b._l; + int b1 = b._m; + int b2 = b._h; + return _divideHelper(a0, a1, a2, aNeg, b0, b1, b2, bNeg, what); + } + + static const _RETURN_DIV = 1; + static const _RETURN_REM = 2; + static const _RETURN_MOD = 3; + + static Int64 _divideHelper( + // up to 64 bits unsigned in a2/a1/a0 and b2/b1/b0 + int a0, + int a1, + int a2, + bool aNeg, // input A. + int b0, + int b1, + int b2, + bool bNeg, // input B. + int what) { + int q0 = 0, q1 = 0, q2 = 0; // result Q. + int r0 = 0, r1 = 0, r2 = 0; // result R. + + if (b2 == 0 && b1 == 0 && b0 < (1 << (30 - _BITS))) { + // Small divisor can be handled by single-digit division within Smi range. + // + // Handling small divisors here helps the estimate version below by + // handling cases where the estimate is off by more than a small amount. + + q2 = a2 ~/ b0; + int carry = a2 - q2 * b0; + int d1 = a1 + (carry << _BITS); + q1 = d1 ~/ b0; + carry = d1 - q1 * b0; + int d0 = a0 + (carry << _BITS); + q0 = d0 ~/ b0; + r0 = d0 - q0 * b0; + } else { + // Approximate Q = A ~/ B and R = A - Q * B using doubles. + + // The floating point approximation is very close to the correct value + // when floor(A/B) fits in fewer that 53 bits. + + // We use double arithmetic for intermediate values. Double arithmetic on + // non-negative values is exact under the following conditions: + // + // - The values are integer values that fit in 53 bits. + // - Dividing by powers of two (adjusts exponent only). + // - Floor (zeroes bits with fractional weight). + + const double K2 = 17592186044416.0; // 2^44 + const double K1 = 4194304.0; // 2^22 + + // Approximate double values for [a] and [b]. + double ad = a0 + K1 * a1 + K2 * a2; + double bd = b0 + K1 * b1 + K2 * b2; + // Approximate quotient. + double qd = (ad / bd).floorToDouble(); + + // Extract components of [qd] using double arithmetic. + double q2d = (qd / K2).floorToDouble(); + qd = qd - K2 * q2d; + double q1d = (qd / K1).floorToDouble(); + double q0d = qd - K1 * q1d; + q2 = q2d.toInt(); + q1 = q1d.toInt(); + q0 = q0d.toInt(); + + assert(q0 + K1 * q1 + K2 * q2 == (ad / bd).floorToDouble()); + assert(q2 == 0 || b2 == 0); // Q and B can't both be big since Q*B <= A. + + // P = Q * B, using doubles to hold intermediates. + // We don't need all partial sums since Q*B <= A. + double p0d = q0d * b0; + double p0carry = (p0d / K1).floorToDouble(); + p0d = p0d - p0carry * K1; + double p1d = q1d * b0 + q0d * b1 + p0carry; + double p1carry = (p1d / K1).floorToDouble(); + p1d = p1d - p1carry * K1; + double p2d = q2d * b0 + q1d * b1 + q0d * b2 + p1carry; + assert(p2d <= _MASK2); // No partial sum overflow. + + // R = A - P + int diff0 = a0 - p0d.toInt(); + int diff1 = a1 - p1d.toInt() - ((diff0 >> _BITS) & 1); + int diff2 = a2 - p2d.toInt() - ((diff1 >> _BITS) & 1); + r0 = _MASK & diff0; + r1 = _MASK & diff1; + r2 = _MASK2 & diff2; + + // while (R < 0 || R >= B) + // adjust R towards [0, B) + while (r2 >= _SIGN_BIT_MASK || + r2 > b2 || + (r2 == b2 && (r1 > b1 || (r1 == b1 && r0 >= b0)))) { + // Direction multiplier for adjustment. + int m = (r2 & _SIGN_BIT_MASK) == 0 ? 1 : -1; + // R = R - B or R = R + B + int d0 = r0 - m * b0; + int d1 = r1 - m * (b1 + ((d0 >> _BITS) & 1)); + int d2 = r2 - m * (b2 + ((d1 >> _BITS) & 1)); + r0 = _MASK & d0; + r1 = _MASK & d1; + r2 = _MASK2 & d2; + + // Q = Q + 1 or Q = Q - 1 + d0 = q0 + m; + d1 = q1 + m * ((d0 >> _BITS) & 1); + d2 = q2 + m * ((d1 >> _BITS) & 1); + q0 = _MASK & d0; + q1 = _MASK & d1; + q2 = _MASK2 & d2; + } + } + + // 0 <= R < B + assert(Int64.ZERO <= Int64._bits(r0, r1, r2)); + assert(r2 < b2 || // Handles case where B = -(MIN_VALUE) + Int64._bits(r0, r1, r2) < Int64._bits(b0, b1, b2)); + + assert(what == _RETURN_DIV || what == _RETURN_MOD || what == _RETURN_REM); + if (what == _RETURN_DIV) { + if (aNeg != bNeg) return _negate(q0, q1, q2); + return Int64._masked(q0, q1, q2); // Masking for type inferrer. + } + + if (!aNeg) { + return Int64._masked(r0, r1, r2); // Masking for type inferrer. + } + + if (what == _RETURN_MOD) { + if (r0 == 0 && r1 == 0 && r2 == 0) { + return ZERO; + } else { + return _sub(b0, b1, b2, r0, r1, r2); + } + } else { + return _negate(r0, r1, r2); + } + } +} diff --git a/pkgs/fixnum/lib/src/intx.dart b/pkgs/fixnum/lib/src/intx.dart new file mode 100644 index 00000000..d51a5835 --- /dev/null +++ b/pkgs/fixnum/lib/src/intx.dart @@ -0,0 +1,207 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'int32.dart'; +import 'int64.dart'; + +/// A fixed-precision integer. +abstract class IntX implements Comparable { + /// Addition operator. + IntX operator +(Object other); + + /// Subtraction operator. + IntX operator -(Object other); + + /// Negate operator. + /// + /// Note that `-MIN_VALUE` is equal to `MIN_VALUE` due to overflow. + IntX operator -(); + + /// Multiplication operator. + IntX operator *(Object other); + + /// Euclidean modulo operator. + /// + /// Returns the remainder of the euclidean division. The euclidean division + /// of two integers `a` and `b` yields two integers `q` and `r` such that + /// `a == b * q + r` and `0 <= r < a.abs()`. + IntX operator %(Object other); + + /// Truncating division operator. + IntX operator ~/(Object other); + + /// Returns the remainder of the truncating division of this integer by + /// [other]. + IntX remainder(Object other); + + /// Bitwise and operator. + IntX operator &(Object other); + + /// Bitwise or operator. + IntX operator |(Object other); + + /// Bitwise xor operator. + IntX operator ^(Object other); + + /// Bitwise negate operator. + IntX operator ~(); + + /// Left bit-shift operator. + /// + /// Returns the result of shifting the bits of this integer by [shiftAmount] + /// bits to the left. Low-order bits are filled with zeros. + IntX operator <<(int shiftAmount); + + /// Right bit-shift operator. + /// + /// Returns the result of shifting the bits of this integer by [shiftAmount] + /// bits to the right. High-order bits are filled with zero in the case where + /// this integer is positive, or one in the case where it is negative. + IntX operator >>(int shiftAmount); + + /// Unsigned right-shift operator. + /// + /// Returns the result of shifting the bits of this integer by [shiftAmount] + /// bits to the right. High-order bits are filled with zeros. + IntX shiftRightUnsigned(int shiftAmount); + + @override + int compareTo(Object other); + + /// Returns `true` if and only if [other] is an int or IntX equal in + /// value to this integer. + @override + bool operator ==(Object other); + + /// Relational less than operator. + bool operator <(Object other); + + /// Relational less than or equal to operator. + bool operator <=(Object other); + + /// Relational greater than operator. + bool operator >(Object other); + + /// Relational greater than or equal to operator. + bool operator >=(Object other); + + /// Returns `true` if and only if this integer is even. + bool get isEven; + + /// Returns `true` if and only if this integer is the maximum signed value + /// that can be represented within its bit size. + bool get isMaxValue; + + /// Returns `true` if and only if this integer is the minimum signed value + /// that can be represented within its bit size. + bool get isMinValue; + + /// Returns `true` if and only if this integer is less than zero. + bool get isNegative; + + /// Returns `true` if and only if this integer is odd. + bool get isOdd; + + /// Returns `true` if and only if this integer is zero. + bool get isZero; + + @override + int get hashCode; + + /// Returns the absolute value of this integer. + IntX abs(); + + /// Clamps this integer to be in the range [lowerLimit] - [upperLimit]. + IntX clamp(Object lowerLimit, Object upperLimit); + + /// Returns the minimum number of bits required to store this integer. + /// + /// The number of bits excludes the sign bit, which gives the natural length + /// for non-negative (unsigned) values. Negative values are complemented to + /// return the bit position of the first bit that differs from the sign bit. + /// + /// To find the the number of bits needed to store the value as a signed + /// value, add one, i.e. use `x.bitLength + 1`. + int get bitLength; + + /// Returns the number of high-order zeros in this integer's bit + /// representation. + int numberOfLeadingZeros(); + + /// Returns the number of low-order zeros in this integer's bit + /// representation. + int numberOfTrailingZeros(); + + /// Returns the least significant [width] bits of this integer, extending the + /// highest retained bit to the sign. This is the same as truncating the + /// value 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]. + /// + /// 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); + IntX toSigned(int width); + + /// 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]. + /// + /// 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); + IntX toUnsigned(int width); + + /// Returns a byte-sequence representation of this integer. + /// + /// Returns a list of int, starting with the least significant byte. + List toBytes(); + + /// Returns the double representation of this integer. + /// + /// On some platforms, inputs with large absolute values (i.e., > 2^52) may + /// lose some of their low-order bits. + double toDouble(); + + /// Returns the int representation of this integer. + /// + /// On some platforms, inputs with large absolute values (i.e., > 2^52) may + /// lose some of their low-order bits. + int toInt(); + + /// Returns an Int32 representation of this integer. + /// + /// Narrower values are sign-extended and wider values have their high bits + /// truncated. + Int32 toInt32(); + + /// Returns an Int64 representation of this integer. + Int64 toInt64(); + + /// Returns a string representing the value of this integer in decimal + /// notation; example: `'13'`. + @override + String toString(); + + /// Returns a string representing the value of this integer in hexadecimal + /// notation. + /// + /// Example: `Int64(0xf01d).toHexString()` returns `'F01D'`. + /// + /// The string may interprets the number as *unsigned*, and has no leading + /// minus, even if the value [isNegative]. + /// + /// Example: `Int64(-1).toHexString()` returns `'FFFFFFFFFFFFFFFF'`. + String toHexString(); + + /// Returns a string representing the value of this integer in the given + /// radix. + /// + /// [radix] must be an integer in the range 2 .. 16, inclusive. + String toRadixString(int radix); +} diff --git a/pkgs/fixnum/lib/src/utilities.dart b/pkgs/fixnum/lib/src/utilities.dart new file mode 100644 index 00000000..8b3957cf --- /dev/null +++ b/pkgs/fixnum/lib/src/utilities.dart @@ -0,0 +1,72 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Shared functionality used by multiple classes and their implementations. + +int validateRadix(int radix) => + RangeError.checkValueInInterval(radix, 2, 36, 'radix'); + +/// Converts radix digits into their numeric values. +/// +/// Converts the characters `0`-`9` into the values 0 through 9, +/// and the letters `a`-`z` or `A`-`Z` into values 10 through 35, +/// and return that value. +/// Any other character returns a value above 35, which means it's +/// not a valid digit in any radix in the range 2 through 36. +int decodeDigit(int c) { + // Hex digit char codes + const c0 = 48; // '0'.codeUnitAt(0) + const ca = 97; // 'a'.codeUnitAt(0) + + var digit = c ^ c0; + if (digit < 10) return digit; + var letter = (c | 0x20) - ca; + if (letter >= 0) { + // Returns values above 36 for invalid digits. + // The value is checked against the actual radix where the return + // value is used, so this is safe. + return letter + 10; + } else { + return 255; // Never a valid radix. + } +} + +// Assumes i is <= 32-bit +int numberOfLeadingZeros(int i) { + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + return bitCount(~i); +} + +int numberOfTrailingZeros(int i) => bitCount((i & -i) - 1); + +// Assumes i is <= 32-bit. +int bitCount(int i) { + // See "Hacker's Delight", section 5-1, "Counting 1-Bits". + + // The basic strategy is to use "divide and conquer" to + // add pairs (then quads, etc.) of bits together to obtain + // sub-counts. + // + // A straightforward approach would look like: + // + // i = (i & 0x55555555) + ((i >> 1) & 0x55555555); + // i = (i & 0x33333333) + ((i >> 2) & 0x33333333); + // i = (i & 0x0F0F0F0F) + ((i >> 4) & 0x0F0F0F0F); + // i = (i & 0x00FF00FF) + ((i >> 8) & 0x00FF00FF); + // i = (i & 0x0000FFFF) + ((i >> 16) & 0x0000FFFF); + // + // The code below removes unnecessary &'s and uses a + // trick to remove one instruction in the first line. + + i -= (i >> 1) & 0x55555555; + i = (i & 0x33333333) + ((i >> 2) & 0x33333333); + i = (i + (i >> 4)) & 0x0F0F0F0F; + i += i >> 8; + i += i >> 16; + return i & 0x0000003F; +} diff --git a/pkgs/fixnum/pubspec.yaml b/pkgs/fixnum/pubspec.yaml new file mode 100644 index 00000000..efa61eba --- /dev/null +++ b/pkgs/fixnum/pubspec.yaml @@ -0,0 +1,13 @@ +name: fixnum +version: 1.1.1 +description: >- + Library for 32- and 64-bit signed fixed-width integers with consistent + behavior between native and JS runtimes. +repository: https://github.com/dart-lang/core/tree/main/pkgs/fixnum + +environment: + sdk: ^3.1.0 + +dev_dependencies: + dart_flutter_team_lints: ^3.0.0 + test: ^1.16.6 diff --git a/pkgs/fixnum/test/all_tests.dart b/pkgs/fixnum/test/all_tests.dart new file mode 100644 index 00000000..a4e2e749 --- /dev/null +++ b/pkgs/fixnum/test/all_tests.dart @@ -0,0 +1,11 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'int32_test.dart' as int32_test; +import 'int64_test.dart' as int64_test; + +void main() { + int32_test.main(); + int64_test.main(); +} diff --git a/pkgs/fixnum/test/int32_test.dart b/pkgs/fixnum/test/int32_test.dart new file mode 100644 index 00000000..5e6c36ad --- /dev/null +++ b/pkgs/fixnum/test/int32_test.dart @@ -0,0 +1,413 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:fixnum/fixnum.dart'; +import 'package:test/test.dart'; + +void main() { + group('isX tests', () { + test('isEven', () { + expect((-Int32.ONE).isEven, false); + expect(Int32.ZERO.isEven, true); + expect(Int32.ONE.isEven, false); + expect(Int32.TWO.isEven, true); + }); + test('isMaxValue', () { + expect(Int32.MIN_VALUE.isMaxValue, false); + expect(Int32.ZERO.isMaxValue, false); + expect(Int32.MAX_VALUE.isMaxValue, true); + }); + test('isMinValue', () { + expect(Int32.MIN_VALUE.isMinValue, true); + expect(Int32.ZERO.isMinValue, false); + expect(Int32.MAX_VALUE.isMinValue, false); + }); + test('isNegative', () { + expect(Int32.MIN_VALUE.isNegative, true); + expect(Int32.ZERO.isNegative, false); + expect(Int32.ONE.isNegative, false); + }); + test('isOdd', () { + expect((-Int32.ONE).isOdd, true); + expect(Int32.ZERO.isOdd, false); + expect(Int32.ONE.isOdd, true); + expect(Int32.TWO.isOdd, false); + }); + test('isZero', () { + expect(Int32.MIN_VALUE.isZero, false); + expect(Int32.ZERO.isZero, true); + expect(Int32.MAX_VALUE.isZero, false); + }); + test('bitLength', () { + expect(Int32(-2).bitLength, 1); + expect((-Int32.ONE).bitLength, 0); + expect(Int32.ZERO.bitLength, 0); + expect(Int32.ONE.bitLength, 1); + expect(Int32(2).bitLength, 2); + expect(Int32.MAX_VALUE.bitLength, 31); + expect(Int32.MIN_VALUE.bitLength, 31); + }); + }); + + group('arithmetic operators', () { + var n1 = Int32(1234); + var n2 = Int32(9876); + var n3 = Int32(-1234); + var n4 = Int32(-9876); + + test('+', () { + expect(n1 + n2, Int32(11110)); + expect(n3 + n2, Int32(8642)); + expect(n3 + n4, Int32(-11110)); + expect(n3 + Int64(1), Int64(-1233)); + expect(Int32.MAX_VALUE + 1, Int32.MIN_VALUE); + }); + + test('-', () { + expect(n1 - n2, Int32(-8642)); + expect(n3 - n2, Int32(-11110)); + expect(n3 - n4, Int32(8642)); + expect(n3 - Int64(1), Int64(-1235)); + expect(Int32.MIN_VALUE - 1, Int32.MAX_VALUE); + }); + + test('unary -', () { + expect(-n1, Int32(-1234)); + expect(-Int32.ZERO, Int32.ZERO); + }); + + test('*', () { + expect(n1 * n2, Int32(12186984)); + expect(n2 * n3, Int32(-12186984)); + expect(n3 * n3, Int32(1522756)); + expect(n3 * n2, Int32(-12186984)); + expect(Int32(0x12345678) * Int32(0x22222222), Int32(-899716112)); + expect(Int32(123456789) * Int32(987654321), Int32(-67153019)); + expect(Int32(0x12345678) * Int64(0x22222222), + Int64.fromInts(0x026D60DC, 0xCA5F6BF0)); + expect(Int32(123456789) * 987654321, Int32(-67153019)); + }); + + test('~/', () { + expect(Int32(829893893) ~/ Int32(1919), Int32(432461)); + expect(Int32(0x12345678) ~/ Int32(0x22), Int32(0x12345678 ~/ 0x22)); + expect(Int32(829893893) ~/ Int64(1919), Int32(432461)); + expect(Int32(0x12345678) ~/ Int64(0x22), Int32(0x12345678 ~/ 0x22)); + expect(Int32(829893893) ~/ 1919, Int32(432461)); + expect(() => Int32(17) ~/ Int32.ZERO, throwsA(isUnsupportedError)); + }); + + test('%', () { + expect(Int32(0x12345678) % Int32(0x22), Int32(0x12345678 % 0x22)); + expect(Int32(0x12345678) % Int64(0x22), Int32(0x12345678 % 0x22)); + }); + + test('remainder', () { + expect(Int32(0x12345678).remainder(Int32(0x22)), + Int32(0x12345678.remainder(0x22))); + expect(Int32(0x12345678).remainder(Int32(-0x22)), + Int32(0x12345678.remainder(-0x22))); + expect(Int32(-0x12345678).remainder(Int32(-0x22)), + Int32(-0x12345678.remainder(-0x22))); + expect(Int32(-0x12345678).remainder(Int32(0x22)), + Int32(-0x12345678.remainder(0x22))); + expect(Int32(0x12345678).remainder(Int64(0x22)), + Int32(0x12345678.remainder(0x22))); + }); + + test('abs', () { + // NOTE: Int32.MIN_VALUE.abs() is undefined + expect((Int32.MIN_VALUE + 1).abs(), Int32.MAX_VALUE); + expect(Int32(-1).abs(), Int32(1)); + expect(Int32(0).abs(), Int32(0)); + expect(Int32(1).abs(), Int32(1)); + expect(Int32.MAX_VALUE.abs(), Int32.MAX_VALUE); + }); + + test('clamp', () { + var val = Int32(17); + expect(val.clamp(20, 30), Int32(20)); + expect(val.clamp(10, 20), Int32(17)); + expect(val.clamp(10, 15), Int32(15)); + + expect(val.clamp(Int32(20), Int32(30)), Int32(20)); + expect(val.clamp(Int32(10), Int32(20)), Int32(17)); + expect(val.clamp(Int32(10), Int32(15)), Int32(15)); + + expect(val.clamp(Int64(20), Int64(30)), Int32(20)); + expect(val.clamp(Int64(10), Int64(20)), Int32(17)); + expect(val.clamp(Int64(10), Int64(15)), Int32(15)); + expect(val.clamp(Int64.MIN_VALUE, Int64(30)), Int32(17)); + expect(val.clamp(Int64(10), Int64.MAX_VALUE), Int32(17)); + + expect(() => val.clamp(30.5, 40.5), throwsArgumentError); + expect(() => val.clamp(5.5, 10.5), throwsArgumentError); + expect(() => val.clamp('a', 1), throwsArgumentError); + expect(() => val.clamp(1, 'b'), throwsArgumentError); + expect(() => val.clamp('a', 1), throwsArgumentError); + }); + }); + + group('leading/trailing zeros', () { + test('numberOfLeadingZeros', () { + expect(Int32(0).numberOfLeadingZeros(), 32); + expect(Int32(1).numberOfLeadingZeros(), 31); + expect(Int32(0xffff).numberOfLeadingZeros(), 16); + expect(Int32(-1).numberOfLeadingZeros(), 0); + }); + test('numberOfTrailingZeros', () { + expect(Int32(0).numberOfTrailingZeros(), 32); + expect(Int32(0x80000000).numberOfTrailingZeros(), 31); + expect(Int32(1).numberOfTrailingZeros(), 0); + expect(Int32(0x10000).numberOfTrailingZeros(), 16); + }); + }); + + group('comparison operators', () { + test('compareTo', () { + expect(Int32(0).compareTo(-1), 1); + expect(Int32(0).compareTo(0), 0); + expect(Int32(0).compareTo(1), -1); + expect(Int32(0).compareTo(Int32(-1)), 1); + expect(Int32(0).compareTo(Int32(0)), 0); + expect(Int32(0).compareTo(Int32(1)), -1); + expect(Int32(0).compareTo(Int64(-1)), 1); + expect(Int32(0).compareTo(Int64(0)), 0); + expect(Int32(0).compareTo(Int64(1)), -1); + }); + + test('<', () { + expect(Int32(17) < Int32(18), true); + expect(Int32(17) < Int32(17), false); + expect(Int32(17) < Int32(16), false); + expect(Int32(17) < Int64(18), true); + expect(Int32(17) < Int64(17), false); + expect(Int32(17) < Int64(16), false); + expect(Int32.MIN_VALUE < Int32.MAX_VALUE, true); + expect(Int32.MAX_VALUE < Int32.MIN_VALUE, false); + }); + + test('<=', () { + expect(Int32(17) <= Int32(18), true); + expect(Int32(17) <= Int32(17), true); + expect(Int32(17) <= Int32(16), false); + expect(Int32(17) <= Int64(18), true); + expect(Int32(17) <= Int64(17), true); + expect(Int32(17) <= Int64(16), false); + expect(Int32.MIN_VALUE <= Int32.MAX_VALUE, true); + expect(Int32.MAX_VALUE <= Int32.MIN_VALUE, false); + }); + + test('==', () { + expect(Int32(17), isNot(equals(Int32(18)))); + expect(Int32(17), equals(Int32(17))); + expect(Int32(17), isNot(equals(Int32(16)))); + expect(Int32(17), isNot(equals(Int64(18)))); + expect(Int32(17), equals(Int64(17))); + expect(Int32(17), isNot(equals(Int64(16)))); + expect(Int32.MIN_VALUE, isNot(equals(Int32.MAX_VALUE))); + expect(Int32(17), isNot(equals(18))); + expect(Int32(17) == 17, isTrue); // ignore: unrelated_type_equality_checks + expect(Int32(17), isNot(equals(16))); + expect(Int32(17), isNot(equals(Object()))); + expect(Int32(17), isNot(equals(null))); + }); + + test('>=', () { + expect(Int32(17) >= Int32(18), false); + expect(Int32(17) >= Int32(17), true); + expect(Int32(17) >= Int32(16), true); + expect(Int32(17) >= Int64(18), false); + expect(Int32(17) >= Int64(17), true); + expect(Int32(17) >= Int64(16), true); + expect(Int32.MIN_VALUE >= Int32.MAX_VALUE, false); + expect(Int32.MAX_VALUE >= Int32.MIN_VALUE, true); + }); + + test('>', () { + expect(Int32(17) > Int32(18), false); + expect(Int32(17) > Int32(17), false); + expect(Int32(17) > Int32(16), true); + expect(Int32(17) > Int64(18), false); + expect(Int32(17) > Int64(17), false); + expect(Int32(17) > Int64(16), true); + expect(Int32.MIN_VALUE > Int32.MAX_VALUE, false); + expect(Int32.MAX_VALUE > Int32.MIN_VALUE, true); + }); + }); + + group('bitwise operators', () { + test('&', () { + expect(Int32(0x12345678) & Int32(0x22222222), + Int32(0x12345678 & 0x22222222)); + expect(Int32(0x12345678) & Int64(0x22222222), + Int64(0x12345678 & 0x22222222)); + }); + + test('|', () { + expect(Int32(0x12345678) | Int32(0x22222222), + Int32(0x12345678 | 0x22222222)); + expect(Int32(0x12345678) | Int64(0x22222222), + Int64(0x12345678 | 0x22222222)); + }); + + test('^', () { + expect(Int32(0x12345678) ^ Int32(0x22222222), + Int32(0x12345678 ^ 0x22222222)); + expect(Int32(0x12345678) ^ Int64(0x22222222), + Int64(0x12345678 ^ 0x22222222)); + }); + + test('~', () { + expect(~Int32(0x12345678), Int32(~0x12345678)); + expect(-Int32(0x12345678), Int64(-0x12345678)); + }); + }); + + group('bitshift operators', () { + test('<<', () { + expect(Int32(0x12345678) << 7, Int32(0x12345678 << 7)); + expect(Int32(0x12345678) << 32, Int32.ZERO); + expect(Int32(0x12345678) << 33, Int32.ZERO); + expect(() => Int32(17) << -1, throwsArgumentError); + }); + + test('>>', () { + expect(Int32(0x12345678) >> 7, Int32(0x12345678 >> 7)); + expect(Int32(0x12345678) >> 32, Int32.ZERO); + expect(Int32(0x12345678) >> 33, Int32.ZERO); + expect(Int32(-42) >> 32, Int32(-1)); + expect(Int32(-42) >> 33, Int32(-1)); + expect(() => Int32(17) >> -1, throwsArgumentError); + }); + + test('shiftRightUnsigned', () { + expect(Int32(0x12345678).shiftRightUnsigned(7), Int32(0x12345678 >> 7)); + expect(Int32(0x12345678).shiftRightUnsigned(32), Int32.ZERO); + expect(Int32(0x12345678).shiftRightUnsigned(33), Int32.ZERO); + expect(Int32(-42).shiftRightUnsigned(32), Int32.ZERO); + expect(Int32(-42).shiftRightUnsigned(33), Int32.ZERO); + expect(() => Int32(17).shiftRightUnsigned(-1), throwsArgumentError); + }); + }); + + group('conversions', () { + test('toSigned', () { + expect(Int32.ONE.toSigned(2), Int32.ONE); + expect(Int32.ONE.toSigned(1), -Int32.ONE); + expect(Int32.MAX_VALUE.toSigned(32), Int32.MAX_VALUE); + expect(Int32.MIN_VALUE.toSigned(32), Int32.MIN_VALUE); + expect(Int32.MAX_VALUE.toSigned(31), -Int32.ONE); + expect(Int32.MIN_VALUE.toSigned(31), Int32.ZERO); + expect(() => Int32.ONE.toSigned(0), throwsRangeError); + expect(() => Int32.ONE.toSigned(33), throwsRangeError); + }); + test('toUnsigned', () { + expect(Int32.ONE.toUnsigned(1), Int32.ONE); + expect(Int32.ONE.toUnsigned(0), Int32.ZERO); + expect(Int32.MAX_VALUE.toUnsigned(32), Int32.MAX_VALUE); + expect(Int32.MIN_VALUE.toUnsigned(32), Int32.MIN_VALUE); + expect(Int32.MAX_VALUE.toUnsigned(31), Int32.MAX_VALUE); + expect(Int32.MIN_VALUE.toUnsigned(31), Int32.ZERO); + expect(() => Int32.ONE.toUnsigned(-1), throwsRangeError); + expect(() => Int32.ONE.toUnsigned(33), throwsRangeError); + }); + test('toDouble', () { + expect(Int32(17).toDouble(), same(17.0)); + expect(Int32(-17).toDouble(), same(-17.0)); + }); + test('toInt', () { + expect(Int32(17).toInt(), 17); + expect(Int32(-17).toInt(), -17); + }); + test('toInt32', () { + expect(Int32(17).toInt32(), Int32(17)); + expect(Int32(-17).toInt32(), Int32(-17)); + }); + test('toInt64', () { + expect(Int32(17).toInt64(), Int64(17)); + expect(Int32(-17).toInt64(), Int64(-17)); + }); + test('toBytes', () { + expect(Int32(0).toBytes(), [0, 0, 0, 0]); + expect(Int32(0x01020304).toBytes(), [4, 3, 2, 1]); + expect(Int32(0x04030201).toBytes(), [1, 2, 3, 4]); + expect(Int32(-1).toBytes(), [0xff, 0xff, 0xff, 0xff]); + }); + }); + + group('parse', () { + test('base 10', () { + void checkInt(int x) { + expect(Int32.parseRadix('$x', 10), Int32(x)); + } + + checkInt(0); + checkInt(1); + checkInt(1000); + checkInt(12345678); + checkInt(2147483647); + checkInt(2147483648); + checkInt(4294967295); + checkInt(4294967296); + expect(() => Int32.parseRadix('xyzzy', -1), throwsArgumentError); + expect(() => Int32.parseRadix('plugh', 10), throwsFormatException); + }); + + test('parseRadix', () { + void check(String s, int r, String x) { + expect(Int32.parseRadix(s, r).toString(), x); + } + + check('deadbeef', 16, '-559038737'); + check('95', 12, '113'); + }); + + test('parseInt', () { + expect(Int32.parseInt('0'), Int32(0)); + expect(Int32.parseInt('1000'), Int32(1000)); + expect(Int32.parseInt('4294967296'), Int32(4294967296)); + }); + + test('parseHex', () { + expect(Int32.parseHex('deadbeef'), Int32(0xdeadbeef)); + expect(Int32.parseHex('cafebabe'), Int32(0xcafebabe)); + expect(Int32.parseHex('8badf00d'), Int32(0x8badf00d)); + }); + }); + + group('string representation', () { + test('toString', () { + expect(Int32(0).toString(), '0'); + expect(Int32(1).toString(), '1'); + expect(Int32(-1).toString(), '-1'); + expect(Int32(1000).toString(), '1000'); + expect(Int32(-1000).toString(), '-1000'); + expect(Int32(123456789).toString(), '123456789'); + expect(Int32(2147483647).toString(), '2147483647'); + expect(Int32(2147483648).toString(), '-2147483648'); + expect(Int32(2147483649).toString(), '-2147483647'); + expect(Int32(2147483650).toString(), '-2147483646'); + expect(Int32(-2147483648).toString(), '-2147483648'); + expect(Int32(-2147483649).toString(), '2147483647'); + expect(Int32(-2147483650).toString(), '2147483646'); + }); + }); + + group('toHexString', () { + test('returns hexadecimal string representation', () { + expect(Int32(-1).toHexString(), '-1'); + expect((Int32(-1) >> 8).toHexString(), '-1'); + expect((Int32(-1) << 8).toHexString(), '-100'); + expect(Int32(123456789).toHexString(), '75bcd15'); + expect(Int32(-1).shiftRightUnsigned(8).toHexString(), 'ffffff'); + }); + }); + + group('toRadixString', () { + test('returns base n string representation', () { + expect(Int32(123456789).toRadixString(5), '223101104124'); + }); + }); +} diff --git a/pkgs/fixnum/test/int64_test.dart b/pkgs/fixnum/test/int64_test.dart new file mode 100644 index 00000000..e74d8936 --- /dev/null +++ b/pkgs/fixnum/test/int64_test.dart @@ -0,0 +1,934 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// We permit local variable types in this test because they statically 'assert' +// that the operations have an expected type. +// +// ignore_for_file: omit_local_variable_types + +import 'package:fixnum/fixnum.dart'; +import 'package:test/test.dart'; + +void main() { + group('fromBytes', () { + test('fromBytes', () { + void checkBytes(List bytes, int h, int l) { + expect(Int64.fromBytes(bytes), Int64.fromInts(h, l)); + } + + checkBytes([0, 0, 0, 0, 0, 0, 0, 0], 0, 0); + checkBytes([1, 0, 0, 0, 0, 0, 0, 0], 0, 1); + checkBytes([1, 2, 3, 4, 5, 6, 7, 8], 0x08070605, 0x04030201); + checkBytes([0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 0xffffffff, + 0xfffffffe); + checkBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 0xffffffff, + 0xffffffff); + }); + test('fromBytesBigEndian', () { + void checkBytes(List bytes, int h, int l) { + expect(Int64.fromBytesBigEndian(bytes), Int64.fromInts(h, l)); + } + + checkBytes([0, 0, 0, 0, 0, 0, 0, 0], 0, 0); + checkBytes([0, 0, 0, 0, 0, 0, 0, 1], 0, 1); + checkBytes([8, 7, 6, 5, 4, 3, 2, 1], 0x08070605, 0x04030201); + checkBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe], 0xffffffff, + 0xfffffffe); + checkBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 0xffffffff, + 0xffffffff); + }); + }); + + void argumentErrorTest( + Object Function(Int64, Object) op, [ + Int64 receiver = Int64.ONE, + ]) { + expect( + () => op(receiver, 'foo'), + throwsA( + isA() + .having((p0) => p0.toString(), 'toString', contains('"foo"')), + ), + ); + } + + group('is-tests', () { + test('isEven', () { + expect((-Int64.ONE).isEven, false); + expect(Int64.ZERO.isEven, true); + expect(Int64.ONE.isEven, false); + expect(Int64.TWO.isEven, true); + }); + test('isMaxValue', () { + expect(Int64.MIN_VALUE.isMaxValue, false); + expect(Int64.ZERO.isMaxValue, false); + expect(Int64.MAX_VALUE.isMaxValue, true); + }); + test('isMinValue', () { + expect(Int64.MIN_VALUE.isMinValue, true); + expect(Int64.ZERO.isMinValue, false); + expect(Int64.MAX_VALUE.isMinValue, false); + }); + test('isNegative', () { + expect(Int64.MIN_VALUE.isNegative, true); + expect(Int64.ZERO.isNegative, false); + expect(Int64.ONE.isNegative, false); + }); + test('isOdd', () { + expect((-Int64.ONE).isOdd, true); + expect(Int64.ZERO.isOdd, false); + expect(Int64.ONE.isOdd, true); + expect(Int64.TWO.isOdd, false); + }); + test('isZero', () { + expect(Int64.MIN_VALUE.isZero, false); + expect(Int64.ZERO.isZero, true); + expect(Int64.MAX_VALUE.isZero, false); + }); + test('bitLength', () { + expect(Int64(-2).bitLength, 1); + expect((-Int64.ONE).bitLength, 0); + expect(Int64.ZERO.bitLength, 0); + expect((Int64.ONE << 21).bitLength, 22); + expect((Int64.ONE << 22).bitLength, 23); + expect((Int64.ONE << 43).bitLength, 44); + expect((Int64.ONE << 44).bitLength, 45); + expect(Int64(2).bitLength, 2); + expect(Int64.MAX_VALUE.bitLength, 63); + expect(Int64.MIN_VALUE.bitLength, 63); + }); + }); + + group('arithmetic operators', () { + Int64 n1 = Int64(1234); + Int64 n2 = Int64(9876); + Int64 n3 = Int64(-1234); + Int64 n4 = Int64(-9876); + Int64 n5 = Int64.fromInts(0x12345678, 0xabcdabcd); + Int64 n6 = Int64.fromInts(0x77773333, 0x22224444); + + test('+', () { + expect(n1 + n2, Int64(11110)); + expect(n3 + n2, Int64(8642)); + expect(n3 + n4, Int64(-11110)); + expect(n5 + n6, Int64.fromInts(0x89ab89ab, 0xcdeff011)); + expect(Int64.MAX_VALUE + 1, Int64.MIN_VALUE); + argumentErrorTest((a, b) => a + b); + }); + + test('-', () { + expect(n1 - n2, Int64(-8642)); + expect(n3 - n2, Int64(-11110)); + expect(n3 - n4, Int64(8642)); + expect(n5 - n6, Int64.fromInts(0x9abd2345, 0x89ab6789)); + expect(Int64.MIN_VALUE - 1, Int64.MAX_VALUE); + argumentErrorTest((a, b) => a - b); + }); + + test('unary -', () { + expect(-n1, Int64(-1234)); + expect(-Int64.ZERO, Int64.ZERO); + }); + + test('*', () { + expect(Int64(1111) * Int64(3), Int64(3333)); + expect(Int64(1111) * Int64(-3), Int64(-3333)); + expect(Int64(-1111) * Int64(3), Int64(-3333)); + expect(Int64(-1111) * Int64(-3), Int64(3333)); + expect(Int64(100) * Int64.ZERO, Int64.ZERO); + + expect( + Int64.fromInts(0x12345678, 0x12345678) * + Int64.fromInts(0x1234, 0x12345678), + Int64.fromInts(0x7ff63f7c, 0x1df4d840)); + expect( + Int64.fromInts(0xf2345678, 0x12345678) * + Int64.fromInts(0x1234, 0x12345678), + Int64.fromInts(0x7ff63f7c, 0x1df4d840)); + expect( + Int64.fromInts(0xf2345678, 0x12345678) * + Int64.fromInts(0xffff1234, 0x12345678), + Int64.fromInts(0x297e3f7c, 0x1df4d840)); + + // RHS Int32 + expect(Int64(123456789) * Int32(987654321), + Int64.fromInts(0x1b13114, 0xfbff5385)); + expect(Int64(123456789) * Int32(987654321), + Int64.fromInts(0x1b13114, 0xfbff5385)); + + // Wraparound + expect(Int64(123456789) * Int64(987654321), + Int64.fromInts(0x1b13114, 0xfbff5385)); + + expect(Int64.MIN_VALUE * Int64(2), Int64.ZERO); + expect(Int64.MIN_VALUE * Int64(1), Int64.MIN_VALUE); + expect(Int64.MIN_VALUE * Int64(-1), Int64.MIN_VALUE); + argumentErrorTest((a, b) => a * b); + }); + + test('~/', () { + Int64 deadBeef = Int64.fromInts(0xDEADBEEF, 0xDEADBEEF); + Int64 ten = Int64(10); + + expect(deadBeef ~/ ten, Int64.fromInts(0xfcaaf97e, 0x63115fe5)); + expect(Int64.ONE ~/ Int64.TWO, Int64.ZERO); + expect( + Int64.MAX_VALUE ~/ Int64.TWO, Int64.fromInts(0x3fffffff, 0xffffffff)); + expect(Int64.ZERO ~/ Int64(1000), Int64.ZERO); + expect(Int64.MIN_VALUE ~/ Int64.MIN_VALUE, Int64.ONE); + expect(Int64(1000) ~/ Int64.MIN_VALUE, Int64.ZERO); + expect(Int64.MIN_VALUE ~/ Int64(8192), Int64(-1125899906842624)); + expect(Int64.MIN_VALUE ~/ Int64(8193), Int64(-1125762484664320)); + expect(Int64(-1000) ~/ Int64(8192), Int64.ZERO); + expect(Int64(-1000) ~/ Int64(8193), Int64.ZERO); + expect(Int64(-1000000000) ~/ Int64(8192), Int64(-122070)); + expect(Int64(-1000000000) ~/ Int64(8193), Int64(-122055)); + expect(Int64(1000000000) ~/ Int64(8192), Int64(122070)); + expect(Int64(1000000000) ~/ Int64(8193), Int64(122055)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x00000400), + Int64.fromInts(0x1fffff, 0xffffffff)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x00040000), + Int64.fromInts(0x1fff, 0xffffffff)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x04000000), + Int64.fromInts(0x1f, 0xffffffff)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000004, 0x00000000), + Int64(536870911)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000400, 0x00000000), + Int64(2097151)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00040000, 0x00000000), + Int64(8191)); + expect( + Int64.MAX_VALUE ~/ Int64.fromInts(0x04000000, 0x00000000), Int64(31)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x00000300), + Int64.fromInts(0x2AAAAA, 0xAAAAAAAA)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x30000000), + Int64.fromInts(0x2, 0xAAAAAAAA)); + expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00300000, 0x00000000), + Int64(0x2AA)); + expect(Int64.MAX_VALUE ~/ Int64(0x123456), + Int64.fromInts(0x708, 0x002E9501)); + expect(Int64.MAX_VALUE % Int64(0x123456), Int64(0x3BDA9)); + expect(Int64(5) ~/ Int64(5), Int64.ONE); + expect(Int64(1000) ~/ Int64(3), Int64(333)); + expect(Int64(1000) ~/ Int64(-3), Int64(-333)); + expect(Int64(-1000) ~/ Int64(3), Int64(-333)); + expect(Int64(-1000) ~/ Int64(-3), Int64(333)); + expect(Int64(3) ~/ Int64(1000), Int64.ZERO); + expect( + Int64.fromInts(0x12345678, 0x12345678) ~/ Int64.fromInts(0x0, 0x123), + Int64.fromInts(0x1003d0, 0xe84f5ae8)); + expect( + Int64.fromInts(0x12345678, 0x12345678) ~/ + Int64.fromInts(0x1234, 0x12345678), + Int64.fromInts(0x0, 0x10003)); + expect( + Int64.fromInts(0xf2345678, 0x12345678) ~/ + Int64.fromInts(0x1234, 0x12345678), + Int64.fromInts(0xffffffff, 0xffff3dfe)); + expect( + Int64.fromInts(0xf2345678, 0x12345678) ~/ + Int64.fromInts(0xffff1234, 0x12345678), + Int64.fromInts(0x0, 0xeda)); + expect(Int64(829893893) ~/ Int32(1919), Int32(432461)); + expect(Int64(829893893) ~/ Int64(1919), Int32(432461)); + expect(Int64(829893893) ~/ 1919, Int32(432461)); + expect(() => Int64(1) ~/ Int64.ZERO, throwsA(isUnsupportedError)); + expect( + Int64.MIN_VALUE ~/ Int64(2), Int64.fromInts(0xc0000000, 0x00000000)); + expect(Int64.MIN_VALUE ~/ Int64(1), Int64.MIN_VALUE); + expect(Int64.MIN_VALUE ~/ Int64(-1), Int64.MIN_VALUE); + expect(() => Int64(17) ~/ Int64.ZERO, throwsA(isUnsupportedError)); + argumentErrorTest((a, b) => a ~/ b); + }); + + test('%', () { + // Define % as Euclidean mod, with positive result for all arguments + expect(Int64.ZERO % Int64(1000), Int64.ZERO); + expect(Int64.MIN_VALUE % Int64.MIN_VALUE, Int64.ZERO); + expect(Int64(1000) % Int64.MIN_VALUE, Int64(1000)); + expect(Int64.MIN_VALUE % Int64(8192), Int64.ZERO); + expect(Int64.MIN_VALUE % Int64(8193), Int64(6145)); + expect(Int64(-1000) % Int64(8192), Int64(7192)); + expect(Int64(-1000) % Int64(8193), Int64(7193)); + expect(Int64(-1000000000) % Int64(8192), Int64(5632)); + expect(Int64(-1000000000) % Int64(8193), Int64(4808)); + expect(Int64(1000000000) % Int64(8192), Int64(2560)); + expect(Int64(1000000000) % Int64(8193), Int64(3385)); + expect(Int64.MAX_VALUE % Int64.fromInts(0x00000000, 0x00000400), + Int64.fromInts(0x0, 0x3ff)); + expect(Int64.MAX_VALUE % Int64.fromInts(0x00000000, 0x00040000), + Int64.fromInts(0x0, 0x3ffff)); + expect(Int64.MAX_VALUE % Int64.fromInts(0x00000000, 0x04000000), + Int64.fromInts(0x0, 0x3ffffff)); + expect(Int64.MAX_VALUE % Int64.fromInts(0x00000004, 0x00000000), + Int64.fromInts(0x3, 0xffffffff)); + expect(Int64.MAX_VALUE % Int64.fromInts(0x00000400, 0x00000000), + Int64.fromInts(0x3ff, 0xffffffff)); + expect(Int64.MAX_VALUE % Int64.fromInts(0x00040000, 0x00000000), + Int64.fromInts(0x3ffff, 0xffffffff)); + expect(Int64.MAX_VALUE % Int64.fromInts(0x04000000, 0x00000000), + Int64.fromInts(0x3ffffff, 0xffffffff)); + expect(Int64(0x12345678).remainder(Int64(0x22)), + Int64(0x12345678.remainder(0x22))); + expect(Int64(0x12345678).remainder(Int64(-0x22)), + Int64(0x12345678.remainder(-0x22))); + expect(Int64(-0x12345678).remainder(Int64(-0x22)), + Int64(-0x12345678.remainder(-0x22))); + expect(Int64(-0x12345678).remainder(Int64(0x22)), + Int64(-0x12345678.remainder(0x22))); + expect(Int32(0x12345678).remainder(Int64(0x22)), + Int64(0x12345678.remainder(0x22))); + argumentErrorTest((a, b) => a % b); + }); + + test('clamp', () { + Int64 val = Int64(17); + expect(val.clamp(20, 30), Int64(20)); + expect(val.clamp(10, 20), Int64(17)); + expect(val.clamp(10, 15), Int64(15)); + + expect(val.clamp(Int32(20), Int32(30)), Int64(20)); + expect(val.clamp(Int32(10), Int32(20)), Int64(17)); + expect(val.clamp(Int32(10), Int32(15)), Int64(15)); + + expect(val.clamp(Int64(20), Int64(30)), Int64(20)); + expect(val.clamp(Int64(10), Int64(20)), Int64(17)); + expect(val.clamp(Int64(10), Int64(15)), Int64(15)); + expect(val.clamp(Int64.MIN_VALUE, Int64(30)), Int64(17)); + expect(val.clamp(Int64(10), Int64.MAX_VALUE), Int64(17)); + + expect(() => val.clamp(1, 'b'), throwsA(isArgumentError)); + expect(() => val.clamp('a', 1), throwsA(isArgumentError)); + }); + }); + + group('leading/trailing zeros', () { + test('numberOfLeadingZeros', () { + void checkZeros(Int64 value, int zeros) { + expect(value.numberOfLeadingZeros(), zeros); + } + + checkZeros(Int64(0), 64); + checkZeros(Int64(1), 63); + checkZeros(Int64.fromInts(0x00000000, 0x003fffff), 42); + checkZeros(Int64.fromInts(0x00000000, 0x00400000), 41); + checkZeros(Int64.fromInts(0x00000fff, 0xffffffff), 20); + checkZeros(Int64.fromInts(0x00001000, 0x00000000), 19); + checkZeros(Int64.fromInts(0x7fffffff, 0xffffffff), 1); + checkZeros(Int64(-1), 0); + }); + + test('numberOfTrailingZeros', () { + void checkZeros(Int64 value, int zeros) { + expect(value.numberOfTrailingZeros(), zeros); + } + + checkZeros(Int64(-1), 0); + checkZeros(Int64(1), 0); + checkZeros(Int64(2), 1); + checkZeros(Int64.fromInts(0x00000000, 0x00200000), 21); + checkZeros(Int64.fromInts(0x00000000, 0x00400000), 22); + checkZeros(Int64.fromInts(0x00000800, 0x00000000), 43); + checkZeros(Int64.fromInts(0x00001000, 0x00000000), 44); + checkZeros(Int64.fromInts(0x80000000, 0x00000000), 63); + checkZeros(Int64(0), 64); + }); + }); + + group('comparison operators', () { + Int64 largeNeg = Int64.fromInts(0x82341234, 0x0); + Int64 largePos = Int64.fromInts(0x12341234, 0x0); + Int64 largePosPlusOne = largePos + Int64(1); + + test('<', () { + expect(Int64(10) < Int64(11), true); + expect(Int64(10) < Int64(10), false); + expect(Int64(10) < Int64(9), false); + expect(Int64(10) < Int32(11), true); + expect(Int64(10) < Int32(10), false); + expect(Int64(10) < Int32(9), false); + expect(Int64(-10) < Int64(-11), false); + expect(Int64.MIN_VALUE < Int64.ZERO, true); + expect(largeNeg < largePos, true); + expect(largePos < largePosPlusOne, true); + expect(largePos < largePos, false); + expect(largePosPlusOne < largePos, false); + expect(Int64.MIN_VALUE < Int64.MAX_VALUE, true); + expect(Int64.MAX_VALUE < Int64.MIN_VALUE, false); + argumentErrorTest((a, b) => a < b); + }); + + test('<=', () { + expect(Int64(10) <= Int64(11), true); + expect(Int64(10) <= Int64(10), true); + expect(Int64(10) <= Int64(9), false); + expect(Int64(10) <= Int32(11), true); + expect(Int64(10) <= Int32(10), true); + expect(Int64(10) <= Int64(9), false); + expect(Int64(-10) <= Int64(-11), false); + expect(Int64(-10) <= Int64(-10), true); + expect(largeNeg <= largePos, true); + expect(largePos <= largeNeg, false); + expect(largePos <= largePosPlusOne, true); + expect(largePos <= largePos, true); + expect(largePosPlusOne <= largePos, false); + expect(Int64.MIN_VALUE <= Int64.MAX_VALUE, true); + expect(Int64.MAX_VALUE <= Int64.MIN_VALUE, false); + argumentErrorTest((a, b) => a <= b); + }); + + test('==', () { + expect(Int64(0), equals(Int64(0))); + expect(Int64(0), isNot(equals(Int64(1)))); + expect(Int64(0), equals(Int32(0))); + expect(Int64(0), isNot(equals(Int32(1)))); + expect(Int64(0) == 0, isTrue); // ignore: unrelated_type_equality_checks + expect(Int64(0), isNot(equals(1))); + expect(Int64(10), isNot(equals(Int64(11)))); + expect(Int64(10), equals(Int64(10))); + expect(Int64(10), isNot(equals(Int64(9)))); + expect(Int64(10), isNot(equals(Int32(11)))); + expect(Int64(10), equals(Int32(10))); + expect(Int64(10), isNot(equals(Int32(9)))); + expect(Int64(10), isNot(equals(11))); + expect(Int64(10) == 10, isTrue); // ignore: unrelated_type_equality_checks + expect(Int64(10), isNot(equals(9))); + expect(Int64(-10), equals(Int64(-10))); + expect(Int64(-10) != Int64(-10), false); + expect( + Int64(-10) == -10, isTrue); // ignore: unrelated_type_equality_checks + expect(Int64(-10), isNot(equals(-9))); + expect(largePos, equals(largePos)); + expect(largePos, isNot(equals(largePosPlusOne))); + expect(largePosPlusOne, isNot(equals(largePos))); + expect(Int64.MIN_VALUE, isNot(equals(Int64.MAX_VALUE))); + expect(Int64(17), isNot(equals(Object()))); + expect(Int64(17), isNot(equals(null))); + }); + + test('>=', () { + expect(Int64(10) >= Int64(11), false); + expect(Int64(10) >= Int64(10), true); + expect(Int64(10) >= Int64(9), true); + expect(Int64(10) >= Int32(11), false); + expect(Int64(10) >= Int32(10), true); + expect(Int64(10) >= Int32(9), true); + expect(Int64(-10) >= Int64(-11), true); + expect(Int64(-10) >= Int64(-10), true); + expect(largePos >= largeNeg, true); + expect(largeNeg >= largePos, false); + expect(largePos >= largePosPlusOne, false); + expect(largePos >= largePos, true); + expect(largePosPlusOne >= largePos, true); + expect(Int64.MIN_VALUE >= Int64.MAX_VALUE, false); + expect(Int64.MAX_VALUE >= Int64.MIN_VALUE, true); + argumentErrorTest((a, b) => a >= b); + }); + + test('>', () { + expect(Int64(10) > Int64(11), false); + expect(Int64(10) > Int64(10), false); + expect(Int64(10) > Int64(9), true); + expect(Int64(10) > Int32(11), false); + expect(Int64(10) > Int32(10), false); + expect(Int64(10) > Int32(9), true); + expect(Int64(-10) > Int64(-11), true); + expect(Int64(10) > Int64(-11), true); + expect(Int64(-10) > Int64(11), false); + expect(largePos > largeNeg, true); + expect(largeNeg > largePos, false); + expect(largePos > largePosPlusOne, false); + expect(largePos > largePos, false); + expect(largePosPlusOne > largePos, true); + expect(Int64.ZERO > Int64.MIN_VALUE, true); + expect(Int64.MIN_VALUE > Int64.MAX_VALUE, false); + expect(Int64.MAX_VALUE > Int64.MIN_VALUE, true); + argumentErrorTest((a, b) => a > b); + }); + }); + + group('bitwise operators', () { + Int64 n1 = Int64(1234); + Int64 n2 = Int64(9876); + Int64 n3 = Int64(-1234); + Int64 n4 = Int64(0x1234) << 32; + Int64 n5 = Int64(0x9876) << 32; + + test('&', () { + expect(n1 & n2, Int64(1168)); + expect(n3 & n2, Int64(8708)); + expect(n4 & n5, Int64(0x1034) << 32); + argumentErrorTest((a, b) => a & b); + }); + + test('|', () { + expect(n1 | n2, Int64(9942)); + expect(n3 | n2, Int64(-66)); + expect(n4 | n5, Int64(0x9a76) << 32); + argumentErrorTest((a, b) => a | b); + }); + + test('^', () { + expect(n1 ^ n2, Int64(8774)); + expect(n3 ^ n2, Int64(-8774)); + expect(n4 ^ n5, Int64(0x8a42) << 32); + argumentErrorTest((a, b) => a ^ b); + }); + + test('~', () { + expect(-Int64(1), Int64(-1)); + expect(-Int64(-1), Int64(1)); + expect(-Int64.MIN_VALUE, Int64.MIN_VALUE); + + expect(~n1, Int64(-1235)); + expect(~n2, Int64(-9877)); + expect(~n3, Int64(1233)); + expect(~n4, Int64.fromInts(0xffffedcb, 0xffffffff)); + expect(~n5, Int64.fromInts(0xffff6789, 0xffffffff)); + }); + }); + + group('bitshift operators', () { + test('<<', () { + expect(Int64.fromInts(0x12341234, 0x45674567) << 10, + Int64.fromInts(0xd048d115, 0x9d159c00)); + expect(Int64.fromInts(0x92341234, 0x45674567) << 10, + Int64.fromInts(0xd048d115, 0x9d159c00)); + expect(Int64(-1) << 5, Int64(-32)); + expect(Int64(-1) << 0, Int64(-1)); + expect(Int64(42) << 64, Int64.ZERO); + expect(Int64(42) << 65, Int64.ZERO); + expect(() => Int64(17) << -1, throwsArgumentError); + }); + + test('>>', () { + expect((Int64.MIN_VALUE >> 13).toString(), '-1125899906842624'); + expect(Int64.fromInts(0x12341234, 0x45674567) >> 10, + Int64.fromInts(0x48d04, 0x8d1159d1)); + expect(Int64.fromInts(0x92341234, 0x45674567) >> 10, + Int64.fromInts(0xffe48d04, 0x8d1159d1)); + expect(Int64.fromInts(0xFFFFFFF, 0xFFFFFFFF) >> 34, Int64(67108863)); + expect(Int64(42) >> 64, Int64.ZERO); + expect(Int64(42) >> 65, Int64.ZERO); + for (int n = 0; n <= 66; n++) { + expect(Int64(-1) >> n, Int64(-1)); + } + expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 8, + Int64.fromInts(0x00723456, 0x789abcde)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 16, + Int64.fromInts(0x00007234, 0x56789abc)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 24, + Int64.fromInts(0x00000072, 0x3456789a)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 28, + Int64.fromInts(0x00000007, 0x23456789)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 32, + Int64.fromInts(0x00000000, 0x72345678)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 36, + Int64.fromInts(0x00000000, 0x07234567)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 40, + Int64.fromInts(0x00000000, 0x00723456)); + expect(Int64.fromInts(0x72345678, 0x9abcde00) >> 44, + Int64.fromInts(0x00000000, 0x00072345)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 48, + Int64.fromInts(0x00000000, 0x00007234)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 8, + Int64.fromInts(0xff923456, 0x789abcde)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 16, + Int64.fromInts(0xffff9234, 0x56789abc)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 24, + Int64.fromInts(0xffffff92, 0x3456789a)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 28, + Int64.fromInts(0xfffffff9, 0x23456789)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 32, + Int64.fromInts(0xffffffff, 0x92345678)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 36, + Int64.fromInts(0xffffffff, 0xf9234567)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 40, + Int64.fromInts(0xffffffff, 0xff923456)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 44, + Int64.fromInts(0xffffffff, 0xfff92345)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 48, + Int64.fromInts(0xffffffff, 0xffff9234)); + expect(() => Int64(17) >> -1, throwsArgumentError); + }); + + test('shiftRightUnsigned', () { + expect(Int64.fromInts(0x12341234, 0x45674567).shiftRightUnsigned(10), + Int64.fromInts(0x48d04, 0x8d1159d1)); + expect(Int64.fromInts(0x92341234, 0x45674567).shiftRightUnsigned(10), + Int64.fromInts(0x248d04, 0x8d1159d1)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(8), + Int64.fromInts(0x00723456, 0x789abcde)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(16), + Int64.fromInts(0x00007234, 0x56789abc)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(24), + Int64.fromInts(0x00000072, 0x3456789a)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(28), + Int64.fromInts(0x00000007, 0x23456789)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(32), + Int64.fromInts(0x00000000, 0x72345678)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(36), + Int64.fromInts(0x00000000, 0x07234567)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(40), + Int64.fromInts(0x00000000, 0x00723456)); + expect(Int64.fromInts(0x72345678, 0x9abcde00).shiftRightUnsigned(44), + Int64.fromInts(0x00000000, 0x00072345)); + expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(48), + Int64.fromInts(0x00000000, 0x00007234)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(8), + Int64.fromInts(0x00923456, 0x789abcde)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(16), + Int64.fromInts(0x00009234, 0x56789abc)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(24), + Int64.fromInts(0x00000092, 0x3456789a)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(28), + Int64.fromInts(0x00000009, 0x23456789)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(32), + Int64.fromInts(0x00000000, 0x92345678)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(36), + Int64.fromInts(0x00000000, 0x09234567)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(40), + Int64.fromInts(0x00000000, 0x00923456)); + expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(44), + Int64.fromInts(0x00000000, 0x00092345)); + expect(Int64.fromInts(0x00000000, 0x00009234), + Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(48)); + expect(Int64(-1).shiftRightUnsigned(64), Int64.ZERO); + expect(Int64(1).shiftRightUnsigned(64), Int64.ZERO); + expect(() => Int64(17).shiftRightUnsigned(-1), throwsArgumentError); + }); + + test('overflow', () { + expect((Int64(1) << 63) >> 1, -Int64.fromInts(0x40000000, 0x00000000)); + expect((Int64(-1) << 32) << 32, Int64(0)); + expect(Int64.MIN_VALUE << 0, Int64.MIN_VALUE); + expect(Int64.MIN_VALUE << 1, Int64(0)); + expect( + (-Int64.fromInts(8, 0)) >> 1, Int64.fromInts(0xfffffffc, 0x00000000)); + expect((-Int64.fromInts(8, 0)).shiftRightUnsigned(1), + Int64.fromInts(0x7ffffffc, 0x0)); + }); + }); + + group('conversions', () { + test('toSigned', () { + expect((Int64.ONE << 44).toSigned(46), Int64.ONE << 44); + expect((Int64.ONE << 44).toSigned(45), -(Int64.ONE << 44)); + expect((Int64.ONE << 22).toSigned(24), Int64.ONE << 22); + expect((Int64.ONE << 22).toSigned(23), -(Int64.ONE << 22)); + expect(Int64.ONE.toSigned(2), Int64.ONE); + expect(Int64.ONE.toSigned(1), -Int64.ONE); + expect(Int64.MAX_VALUE.toSigned(64), Int64.MAX_VALUE); + expect(Int64.MIN_VALUE.toSigned(64), Int64.MIN_VALUE); + expect(Int64.MAX_VALUE.toSigned(63), -Int64.ONE); + expect(Int64.MIN_VALUE.toSigned(63), Int64.ZERO); + expect(() => Int64.ONE.toSigned(0), throwsRangeError); + expect(() => Int64.ONE.toSigned(65), throwsRangeError); + }); + test('toUnsigned', () { + expect((Int64.ONE << 44).toUnsigned(45), Int64.ONE << 44); + expect((Int64.ONE << 44).toUnsigned(44), Int64.ZERO); + expect((Int64.ONE << 22).toUnsigned(23), Int64.ONE << 22); + expect((Int64.ONE << 22).toUnsigned(22), Int64.ZERO); + expect(Int64.ONE.toUnsigned(1), Int64.ONE); + expect(Int64.ONE.toUnsigned(0), Int64.ZERO); + expect(Int64.MAX_VALUE.toUnsigned(64), Int64.MAX_VALUE); + expect(Int64.MIN_VALUE.toUnsigned(64), Int64.MIN_VALUE); + expect(Int64.MAX_VALUE.toUnsigned(63), Int64.MAX_VALUE); + expect(Int64.MIN_VALUE.toUnsigned(63), Int64.ZERO); + expect(() => Int64.ONE.toUnsigned(-1), throwsRangeError); + expect(() => Int64.ONE.toUnsigned(65), throwsRangeError); + }); + test('toDouble', () { + expect(Int64(0).toDouble(), same(0.0)); + expect(Int64(100).toDouble(), same(100.0)); + expect(Int64(-100).toDouble(), same(-100.0)); + expect(Int64(2147483647).toDouble(), same(2147483647.0)); + expect(Int64(2147483648).toDouble(), same(2147483648.0)); + expect(Int64(-2147483647).toDouble(), same(-2147483647.0)); + expect(Int64(-2147483648).toDouble(), same(-2147483648.0)); + expect(Int64(4503599627370495).toDouble(), same(4503599627370495.0)); + expect(Int64(4503599627370496).toDouble(), same(4503599627370496.0)); + expect(Int64(-4503599627370495).toDouble(), same(-4503599627370495.0)); + expect(Int64(-4503599627370496).toDouble(), same(-4503599627370496.0)); + expect(Int64.parseInt('-10000000000000000').toDouble().toStringAsFixed(1), + '-10000000000000000.0'); + expect(Int64.parseInt('-10000000000000001').toDouble().toStringAsFixed(1), + '-10000000000000000.0'); + expect(Int64.parseInt('-10000000000000002').toDouble().toStringAsFixed(1), + '-10000000000000002.0'); + expect(Int64.parseInt('-10000000000000003').toDouble().toStringAsFixed(1), + '-10000000000000004.0'); + expect(Int64.parseInt('-10000000000000004').toDouble().toStringAsFixed(1), + '-10000000000000004.0'); + expect(Int64.parseInt('-10000000000000005').toDouble().toStringAsFixed(1), + '-10000000000000004.0'); + expect(Int64.parseInt('-10000000000000006').toDouble().toStringAsFixed(1), + '-10000000000000006.0'); + expect(Int64.parseInt('-10000000000000007').toDouble().toStringAsFixed(1), + '-10000000000000008.0'); + expect(Int64.parseInt('-10000000000000008').toDouble().toStringAsFixed(1), + '-10000000000000008.0'); + }); + + test('toInt', () { + expect(Int64(0).toInt(), 0); + expect(Int64(100).toInt(), 100); + expect(Int64(-100).toInt(), -100); + expect(Int64(2147483647).toInt(), 2147483647); + expect(Int64(2147483648).toInt(), 2147483648); + expect(Int64(-2147483647).toInt(), -2147483647); + expect(Int64(-2147483648).toInt(), -2147483648); + expect(Int64(4503599627370495).toInt(), 4503599627370495); + expect(Int64(4503599627370496).toInt(), 4503599627370496); + expect(Int64(-4503599627370495).toInt(), -4503599627370495); + expect(Int64(-4503599627370496).toInt(), -4503599627370496); + }); + + test('toInt32', () { + expect(Int64(0).toInt32(), Int32(0)); + expect(Int64(1).toInt32(), Int32(1)); + expect(Int64(-1).toInt32(), Int32(-1)); + expect(Int64(2147483647).toInt32(), Int32(2147483647)); + expect(Int64(2147483648).toInt32(), Int32(-2147483648)); + expect(Int64(2147483649).toInt32(), Int32(-2147483647)); + expect(Int64(2147483650).toInt32(), Int32(-2147483646)); + expect(Int64(-2147483648).toInt32(), Int32(-2147483648)); + expect(Int64(-2147483649).toInt32(), Int32(2147483647)); + expect(Int64(-2147483650).toInt32(), Int32(2147483646)); + expect(Int64(-2147483651).toInt32(), Int32(2147483645)); + }); + + test('toBytes', () { + expect(Int64(0).toBytes(), [0, 0, 0, 0, 0, 0, 0, 0]); + expect(Int64.fromInts(0x08070605, 0x04030201).toBytes(), + [1, 2, 3, 4, 5, 6, 7, 8]); + expect(Int64.fromInts(0x01020304, 0x05060708).toBytes(), + [8, 7, 6, 5, 4, 3, 2, 1]); + expect(Int64(-1).toBytes(), + [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + }); + }); + + test('JavaScript 53-bit integer boundary', () { + Int64 factorial(Int64 n) { + if (n.isZero) { + return Int64(1); + } else { + return n * factorial(n - Int64(1)); + } + } + + Int64 fact18 = factorial(Int64(18)); + Int64 fact17 = factorial(Int64(17)); + expect(fact18 ~/ fact17, Int64(18)); + }); + + test('min, max values', () { + expect(Int64(1) << 63, Int64.MIN_VALUE); + expect(-(Int64.MIN_VALUE + Int64(1)), Int64.MAX_VALUE); + }); + + test('negation', () { + void check(int n) { + // Sign change should commute with conversion. + expect(-Int64(-n), Int64(n)); + expect(Int64(-n), -Int64(n)); + } + + check(10); + check(1000000000000000000); + check(9223372036854774784); // Near Int64.MAX_VALUE with exact double value. + check(-9223372036854775808); // Int64.MIN_VALUE. Is exact double. + }); + + group('parse', () { + test('parseRadix10', () { + void checkInt(int x) { + expect(Int64.parseRadix('$x', 10), Int64(x)); + expect(Int64.tryParseRadix('$x', 10), Int64(x)); + } + + checkInt(0); + checkInt(1); + checkInt(-1); + checkInt(1000); + checkInt(12345678); + checkInt(-12345678); + checkInt(2147483647); + checkInt(2147483648); + checkInt(-2147483647); + checkInt(-2147483648); + checkInt(4294967295); + checkInt(4294967296); + checkInt(-4294967295); + checkInt(-4294967296); + + expect(() => Int64.parseRadix('xyzzy', -1), throwsArgumentError); + expect(() => Int64.parseRadix('plugh', 10), throwsFormatException); + expect(() => Int64.parseRadix('', 10), throwsFormatException); + expect(() => Int64.parseRadix('-', 10), throwsFormatException); + + expect(() => Int64.tryParseRadix('xyzzy', -1), throwsArgumentError); + expect(Int64.tryParseRadix('plugh', 10), null); + expect(Int64.tryParseRadix('', 10), null); + expect(Int64.tryParseRadix('-', 10), null); + }); + + test('parseHex', () { + void checkHex(String hexStr, int h, int l) { + expect(Int64.parseHex(hexStr), Int64.fromInts(h, l)); + expect(Int64.tryParseHex(hexStr), Int64.fromInts(h, l)); + } + + checkHex('0', 0, 0); + checkHex('-0', 0, 0); + checkHex('00', 0, 0); + checkHex('01', 0, 1); + checkHex('-01', 0xffffffff, 0xffffffff); + checkHex('0a', 0, 10); + checkHex('0A', 0, 10); + checkHex('10', 0, 16); + checkHex('3FFFFF', 0, 0x3fffff); + checkHex('400000', 0, 0x400000); + checkHex('FFFFFFFFFFF', 0xfff, 0xffffffff); + checkHex('FFFFFFFFFFFFFFFE', 0xffffffff, 0xfffffffe); + checkHex('FFFFFFFFFFFFFFFF', 0xffffffff, 0xffffffff); + + expect(() => Int64.parseHex(''), throwsFormatException); + expect(() => Int64.parseHex('-'), throwsFormatException); + + expect(Int64.tryParseHex(''), null); + expect(Int64.tryParseHex('-'), null); + }); + + test('parseRadix', () { + void check(String s, int r, String x) { + expect(Int64.parseRadix(s, r).toString(), x); + expect(Int64.tryParseRadix(s, r).toString(), x); + } + + check('ghoul', 36, '27699213'); + check('ghoul', 35, '24769346'); + // Min and max value. + check('-9223372036854775808', 10, '-9223372036854775808'); + check('9223372036854775807', 10, '9223372036854775807'); + // Overflow during parsing. + check('9223372036854775808', 10, '-9223372036854775808'); + + expect(() => Int64.parseRadix('0', 1), throwsRangeError); + expect(() => Int64.parseRadix('0', 37), throwsRangeError); + expect(() => Int64.parseRadix('xyzzy', -1), throwsRangeError); + expect(() => Int64.parseRadix('xyzzy', 10), throwsFormatException); + + expect(() => Int64.tryParseRadix('0', 1), throwsRangeError); + expect(() => Int64.tryParseRadix('0', 37), throwsRangeError); + expect(() => Int64.tryParseRadix('xyzzy', -1), throwsRangeError); + expect(Int64.tryParseRadix('xyzzy', 10), null); + }); + + test('parseRadixN', () { + void check(String s, int r) { + expect(Int64.parseRadix(s, r).toRadixString(r), s); + expect(Int64.tryParseRadix(s, r)!.toRadixString(r), s); + } + + check('2ppp111222333', 33); // This value & radix requires three chunks. + }); + }); + + group('string representation', () { + test('toString', () { + expect(Int64(0).toString(), '0'); + expect(Int64(1).toString(), '1'); + expect(Int64(-1).toString(), '-1'); + expect(Int64(-10).toString(), '-10'); + expect(Int64.MIN_VALUE.toString(), '-9223372036854775808'); + expect(Int64.MAX_VALUE.toString(), '9223372036854775807'); + + int top = 922337201; + int bottom = 967490662; + Int64 fullnum = (Int64(1000000000) * Int64(top)) + Int64(bottom); + expect(fullnum.toString(), '922337201967490662'); + expect((-fullnum).toString(), '-922337201967490662'); + expect(Int64(123456789).toString(), '123456789'); + }); + + test('toHexString', () { + Int64 deadbeef12341234 = Int64.fromInts(0xDEADBEEF, 0x12341234); + expect(Int64.ZERO.toHexString(), '0'); + expect(deadbeef12341234.toHexString(), 'DEADBEEF12341234'); + expect(Int64.fromInts(0x17678A7, 0xDEF01234).toHexString(), + '17678A7DEF01234'); + expect(Int64(123456789).toHexString(), '75BCD15'); + }); + + test('toRadixString', () { + expect(Int64(123456789).toRadixString(5), '223101104124'); + expect(Int64.MIN_VALUE.toRadixString(2), + '-1000000000000000000000000000000000000000000000000000000000000000'); + expect(Int64.MIN_VALUE.toRadixString(3), + '-2021110011022210012102010021220101220222'); + expect(Int64.MIN_VALUE.toRadixString(4), + '-20000000000000000000000000000000'); + expect(Int64.MIN_VALUE.toRadixString(5), '-1104332401304422434310311213'); + expect(Int64.MIN_VALUE.toRadixString(6), '-1540241003031030222122212'); + expect(Int64.MIN_VALUE.toRadixString(7), '-22341010611245052052301'); + expect(Int64.MIN_VALUE.toRadixString(8), '-1000000000000000000000'); + expect(Int64.MIN_VALUE.toRadixString(9), '-67404283172107811828'); + expect(Int64.MIN_VALUE.toRadixString(10), '-9223372036854775808'); + expect(Int64.MIN_VALUE.toRadixString(11), '-1728002635214590698'); + expect(Int64.MIN_VALUE.toRadixString(12), '-41a792678515120368'); + expect(Int64.MIN_VALUE.toRadixString(13), '-10b269549075433c38'); + expect(Int64.MIN_VALUE.toRadixString(14), '-4340724c6c71dc7a8'); + expect(Int64.MIN_VALUE.toRadixString(15), '-160e2ad3246366808'); + expect(Int64.MIN_VALUE.toRadixString(16), '-8000000000000000'); + expect(Int64.MAX_VALUE.toRadixString(2), + '111111111111111111111111111111111111111111111111111111111111111'); + expect(Int64.MAX_VALUE.toRadixString(3), + '2021110011022210012102010021220101220221'); + expect( + Int64.MAX_VALUE.toRadixString(4), '13333333333333333333333333333333'); + expect(Int64.MAX_VALUE.toRadixString(5), '1104332401304422434310311212'); + expect(Int64.MAX_VALUE.toRadixString(6), '1540241003031030222122211'); + expect(Int64.MAX_VALUE.toRadixString(7), '22341010611245052052300'); + expect(Int64.MAX_VALUE.toRadixString(8), '777777777777777777777'); + expect(Int64.MAX_VALUE.toRadixString(9), '67404283172107811827'); + expect(Int64.MAX_VALUE.toRadixString(10), '9223372036854775807'); + expect(Int64.MAX_VALUE.toRadixString(11), '1728002635214590697'); + expect(Int64.MAX_VALUE.toRadixString(12), '41a792678515120367'); + expect(Int64.MAX_VALUE.toRadixString(13), '10b269549075433c37'); + expect(Int64.MAX_VALUE.toRadixString(14), '4340724c6c71dc7a7'); + expect(Int64.MAX_VALUE.toRadixString(15), '160e2ad3246366807'); + expect(Int64.MAX_VALUE.toRadixString(16), '7fffffffffffffff'); + expect(() => Int64(42).toRadixString(-1), throwsArgumentError); + expect(() => Int64(42).toRadixString(0), throwsArgumentError); + expect(() => Int64(42).toRadixString(37), throwsArgumentError); + }); + + test('toStringUnsigned', () { + List values = []; + for (int high = 0; high < 16; high++) { + for (int low = -2; low <= 2; low++) { + values.add((Int64(high) << (64 - 4)) + Int64(low)); + } + } + + for (Int64 value in values) { + for (int radix = 2; radix <= 36; radix++) { + String s1 = value.toRadixStringUnsigned(radix); + Int64 v2 = Int64.parseRadix(s1, radix); + expect(v2, value); + String s2 = v2.toRadixStringUnsigned(radix); + expect(s2, s1); + } + String s3 = value.toStringUnsigned(); + Int64 v4 = Int64.parseInt(s3); + expect(v4, value); + String s4 = v4.toStringUnsigned(); + expect(s4, s3); + } + }); + }); +} diff --git a/pkgs/fixnum/test/int_64_vm_test.dart b/pkgs/fixnum/test/int_64_vm_test.dart new file mode 100644 index 00000000..2d28da3e --- /dev/null +++ b/pkgs/fixnum/test/int_64_vm_test.dart @@ -0,0 +1,48 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('vm') +library; + +import 'package:fixnum/fixnum.dart'; +import 'package:test/test.dart'; + +void main() { + group('conversions', () { + test('toInt', () { + expect(Int64.parseInt('-10000000000000000').toInt(), + same(-10000000000000000)); + expect(Int64.parseInt('-10000000000000001').toInt(), + same(-10000000000000001)); + expect(Int64.parseInt('-10000000000000002').toInt(), + same(-10000000000000002)); + expect(Int64.parseInt('-10000000000000003').toInt(), + same(-10000000000000003)); + expect(Int64.parseInt('-10000000000000004').toInt(), + same(-10000000000000004)); + expect(Int64.parseInt('-10000000000000005').toInt(), + same(-10000000000000005)); + expect(Int64.parseInt('-10000000000000006').toInt(), + same(-10000000000000006)); + expect(Int64.parseInt('-10000000000000007').toInt(), + same(-10000000000000007)); + expect(Int64.parseInt('-10000000000000008').toInt(), + same(-10000000000000008)); + }); + }); + + test('', () { + void check(int n) { + // Sign change should commute with conversion. + expect(-Int64(-n), Int64(n)); + expect(Int64(-n), -Int64(n)); + } + + check(10); + check(1000000000000000000); + check(9223372000000000000); // near Int64.MAX_VALUE, has exact double value + check(9223372036854775807); // Int64.MAX_VALUE, rounds up to -MIN_VALUE + check(-9223372036854775808); // Int64.MIN_VALUE + }); +}