Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Dart (3.13)  Module 2 – Values and types: variables, numbers, strings and operators

Numbers in Dart: int, double, BigInt and the web

Dart's int, double, num and BigInt: ~/ and Euclidean %, rounding, parsing, money in paise, random numbers, and where JavaScript and Wasm builds differ.

  • Beginner
  • 20 minutes
  • Examples run with dart 3.13.5 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Choose int, double, num or BigInt for a value, and keep money as a whole number of paise
  • Predict what /, ~/, % and remainder() give for negative numbers and for zero
  • Parse numbers from text with parse and tryParse, and write long literals with digit separators
  • Predict which results differ between the Dart VM, a JavaScript build and a WebAssembly build, and test on all three
  • Use Random with a seed for repeatable runs and Random.secure() for values nobody may guess

Before you start

What you’ll learn · Before you start

What you will learn

  • Choose int, double, num or BigInt for a value, and keep money as a whole number of paise
  • Predict what /, ~/, % and remainder() give for negative numbers and for zero
  • Parse numbers from text with parse and tryParse, and write long literals with digit separators
  • Predict which results differ between the Dart VM, a JavaScript build and a WebAssembly build, and test on all three
  • Use Random with a seed for repeatable runs and Random.secure() for values nobody may guess

Before you start

On this page

In Dart, 0.1 + 0.2 prints 0.30000000000000004, and doubling 1 sixty-three times prints -9223372036854775808, a negative number. Neither is a bug. Both follow from how Dart stores its two kinds of number, and the second answer changes again when the same code is compiled to JavaScript.

You will pick a type for a count, a measurement and an amount of money, divide and round negative numbers without surprises and read numbers from text safely. Then recorded runs show where the Dart VM, a JavaScript build and a WebAssembly build part ways, and how dart test checks all three.

int counts, double measures, and num takes either

Dart stores numbers in two types: int for whole numbers and double for numbers with a fraction. The literal decides which one you get. Digits alone make an int, in hexadecimal too, and a decimal point or an exponent makes a double. Both are subtypes of num, the type for a value that may be either, such as a parameter that accepts 3 as well as 2.5. Run this program and compare each value with the type it prints:

kinds.dart: which literal makes which type Dart · kinds.dart
// int and double are two types of number, and num is the type above both.
void main() {
  var seats = 42; // an int literal: no decimal point
  var mask = 0xFF; // also an int, written in hexadecimal
  var price = 1.5; // a decimal point makes a double
  var big = 1.42e5; // so does an exponent
  double z = 1; // an int literal where a double is expected becomes 1.0
  num either = 3; // a num may hold an int ...
  either += 0.5; // ... and then a double

  print('$seats ${seats.runtimeType}, $mask ${mask.runtimeType}');
  print('$price ${price.runtimeType}, $big ${big.runtimeType}, $z');
  print('either = $either, ${either.runtimeType}');
  print('7 / 2 = ${7 / 2}, 6 / 2 = ${6 / 2}'); // / always gives a double
  print('1 == 1.0: ${1 == 1.0}, identical(1, 1.0): ${identical(1, 1.0)}');
  print('1 is double: ${(1 as Object) is double}');
}

Output

42 int, 255 int
1.5 double, 142000.0 double, 1.0
either = 3.5, double
7 / 2 = 3.5, 6 / 2 = 3.0
1 == 1.0: true, identical(1, 1.0): false
1 is double: false

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run kinds.dart

A few lines deserve a second look. double z = 1 printed 1.0, because an integer literal where a double is expected becomes a double. 7 / 2 and 6 / 2 both printed doubles, 3.5 and 3.0: the / operator always returns a double, even when the division comes out even. And 1 equals 1.0 with == without being identical to it, or a double at all, since on the Dart VM: The virtual machine that runs Dart programs from source during development, compiling them just in time; dart run uses it, and it makes hot reload and debugging possible. More in “What Dart is and where it runs” one is an int object and the other a double.

Because / always gives a double, its result cannot go into an int variable:

half.dart: the result of / stored in an int Dart · half.dart
// Half of 9 guests, as an int: the / operator does not give one.
void main() {
  int perTable = 9 / 2;
  print(perTable);
}

Output (exit status 254)

Printed as an error (standard error)

half.dart:3:20: Error: A value of type 'double' can't be assigned to a variable of type 'int'.
  int perTable = 9 / 2;
                   ^

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run half.dart

The compiler refused with “A value of type ‘double’ can’t be assigned to a variable of type ‘int’”, and the run ended with status 254, the status of a program that does not compile. To seat 9 guests at 2 tables, first decide what happens to the half: 9 ~/ 2 keeps 4 and (9 / 2).round() gives 5.

Checkpoint

Check: int, double and num

  1. Question 1 of 2 What does 6 / 2 evaluate to in Dart?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 3.0, a double

    Dart has one operator for each kind of answer. / divides and returns a double; ~/ divides and returns the int quotient, rounded toward zero.

  2. Question 2 of 2 Which of these declarations does not compile?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: int perTable = 9 / 2;

    The compiler reported "A value of type 'double' can't be assigned to a variable of type 'int'". Decide what the half should become: 9 ~/ 2 keeps 4, and (9 / 2).round() gives 5.

An int is exact for 63 bits, then it wraps around

On the VM and in native builds an int is a 64-bit two's complement: How a native Dart int stores whole numbers in 64 bits: the top bit counts as minus 2^63, so the range runs from -2^63 to 2^63 - 1, and a result past either end wraps around to the other. integer: one bit for the sign and 63 for the value. It holds every whole number from -2^63 to 2^63 - 1, about 9.2 × 10^18 either way, and inside that range every result is exact. This program doubles 1 sixty-four times, prints the last four results, and then steps past both ends of the range:

wrap.dart: doubling 1 until the bits run out Dart · wrap.dart
// Doubling 1 again and again: an int has 64 bits, and the top bit is the sign.
void main() {
  var n = 1;
  for (var step = 1; step <= 64; step++) {
    n = n * 2;
    if (step >= 61) print('1 doubled $step times: $n');
  }

  const largest = 9223372036854775807; // 2^63 - 1
  const smallest = -largest - 1; // -2^63
  print('largest + 1 = ${largest + 1}');
  print('smallest - 1 = ${smallest - 1}');
}

Output

1 doubled 61 times: 2305843009213693952
1 doubled 62 times: 4611686018427387904
1 doubled 63 times: -9223372036854775808
1 doubled 64 times: 0
largest + 1 = -9223372036854775808
smallest - 1 = 9223372036854775807

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run wrap.dart

Up to 2^62 every doubling was exact. The next one needed the top bit, which is the sign bit, so the result printed as -9223372036854775808, the most negative int, and one more doubling left 0, because the only 1 bit had moved out of the 64. The last two lines show the same wrap from both ends: the largest int plus 1 became the smallest, and the smallest minus 1 became the largest, 9223372036854775807.

This is integer overflow: A whole-number result too large for its type. On native platforms a Dart int that passes 2^63 - 1 wraps around to a negative number, with no exception and no warning., and nothing reports it while the program runs: no exception and no warning, only a wrong number that looks like any other. Convert 4611686018427387904, which is 2^62, to base 2 in the number base converter and read its 64-bit signed form: a single 1, right next to the sign bit that the next doubling reaches.

Waiting for an overflow error

A product that passes 2^63 - 1 does not stop the program. It wraps, and the program carries on with the wrapped value, as the doubling did. When a whole number can grow that far, use BigInt, shown further down, or check the operands before you multiply.

Checkpoint

Check: 64-bit int overflow

  1. Question 1 of 2 On the Dart VM, x holds 4611686018427387904, which is 2^62. What does x * 2 give?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: -9223372036854775808

    A native int has 63 bits for the value and one for the sign. Doubling 2^62 sets the sign bit, which makes the result -2^63, and one more doubling gives 0.

  2. Question 2 of 2 How many bits of a native Dart int hold the value itself, not counting the sign bit?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 63 bits

    A native int is a 64-bit two's-complement integer: 63 bits for the value and 1 for the sign, so it runs from -2^63 to 2^63 - 1.

Four ways to divide, and the one that never goes negative

Dart has four ways to divide whole numbers, and they agree only while both numbers are positive. This program divides 7 and -7 by 3 with each of them, counts back through a week, and divides by zero:

divide.dart: four ways to divide, then zero Dart · divide.dart
// Dividing 7 and -7 by 3 four ways, counting back through a week, then dividing by zero.
void main() {
  for (final a in [7, -7]) {
    print('$a / 3 = ${a / 3}');
    print('$a ~/ 3 = ${a ~/ 3}, $a % 3 = ${a % 3}, $a.remainder(3) = ${a.remainder(3)}');
  }
  print('7 % -3 = ${7 % -3}, 7.remainder(-3) = ${7.remainder(-3)}');

  const wednesday = 2; // days numbered 0 to 6 from Monday
  print('10 days before day 2: % gives ${(wednesday - 10) % 7}, remainder gives ${(wednesday - 10).remainder(7)}');

  print('5 / 0 = ${5 / 0}, -5 / 0 = ${-5 / 0}, 0 / 0 = ${0 / 0}');
  try {
    print(5 ~/ 0);
  } on UnsupportedError catch (e) {
    print('5 ~/ 0 threw $e');
  }
}

Output

7 / 3 = 2.3333333333333335
7 ~/ 3 = 2, 7 % 3 = 1, 7.remainder(3) = 1
-7 / 3 = -2.3333333333333335
-7 ~/ 3 = -2, -7 % 3 = 2, -7.remainder(3) = -1
7 % -3 = 1, 7.remainder(-3) = 1
10 days before day 2: % gives 6, remainder gives -1
5 / 0 = Infinity, -5 / 0 = -Infinity, 0 / 0 = NaN
5 ~/ 0 threw IntegerDivisionByZeroException

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run divide.dart

For 7 the answers match school arithmetic: 7 / 3 is 2.33…, and 7 ~/ 3 is 2 with 1 left over. For -7 they split three ways: ~/ gave -2, remainder() gave -1 and % gave 2. Step through how each one is worked out:

Interactive · stage by stage Dividing -7 by 3 into a quotient and what is left Step through the three integer answers Dart gives for -7 and 3, in the order each one is worked out.
Dividing -7 by 3 into a quotient and what is left, step 5 of 5. Showing -7 / 3, -7 ~/ 3 is -2, remainder is -1, -7 % 3 is 2 (the same as remainder for 7). In focus: -7 % 3 is 2.below 0: add 3-7 / 3-2.333…, a double-7 ~/ 3 is -2drops the fractionremainder is -1-7 minus 3 times -2-7 % 3 is 2the same as remainder for 7

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The exact quotient. Ordinary division gives -2.3333333333333335, a double. Each integer operator starts from this quotient and decides what to do with its fraction.
  2. Rounding toward zero. The ~/ operator drops the fraction and keeps -2. It rounds toward zero, so -2.33 becomes -2 and not -3, exactly as truncate() would.
  3. What is left over. remainder() is what -7 still needs after three lots of -2: -7 minus -6 is -1. It always has the sign of the number being divided, here negative.
  4. Kept from 0 to 2. The % operator wants a result from 0 up to, but not including, 3. A negative remainder gets 3 added: -1 plus 3 is 2, because -7 equals 3 times -3, plus 2.
  5. Where the two differ. For 7 both give 1. They differ only when the number being divided is negative, which is when a position counted backwards needs %, as in a clock or the days of a week.

~/ rounds the quotient toward zero, and remainder() is what is left after that quotient, so it takes the sign of the number being divided. % is the Euclidean modulo: The remainder that Dart's % operator returns: always from 0 up to, but not including, the absolute value of the divisor, so -7 % 3 is 2 while -7.remainder(3) is -1.. The API documentation defines its result r by a == b * q + r and 0 <= r < b.abs(), and adds that “the sign of the returned value r is always positive”, which is why even 7 % -3 gave 1.

That makes % the operator for positions that wrap around. With the days of a week numbered 0 to 6 from Monday, 10 days before Wednesday, day 2, is (2 - 10) % 7, which gave 6, a Sunday. The remainder() of the same numbers gave -1, which is no day at all.

Division by zero splits the operators too. / follows floating-point rules: 5 / 0 is Infinity, -5 / 0 is -Infinity and 0 / 0 is NaN, “not a number”, and the program carries on with them. 5 ~/ 0 has no int to give, so it threw an IntegerDivisionByZeroException, which the program caught as an UnsupportedError. The changelog of Dart 2.15 deprecated that class and asks code to catch Error instead, or to check for a zero divisor before dividing.

Wrapping an index with remainder()

Stepping back from the first item of a list with (i - 1).remainder(length) gives -1, and a list has no position -1. (i - 1) % length gives length - 1, the last position, which is what a carousel or a ring of players needs.

Checkpoint

Check: Integer division and modulo

  1. Question 1 of 2 What do -7 % 3 and -7.remainder(3) give?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 2 and -1

    -7 ~/ 3 is -2 and -7 - 3 × (-2) is -1, the remainder. The Euclidean modulo adds 3 to a negative remainder: -7 equals 3 × (-3) + 2, so -7 % 3 is 2.

  2. Question 2 of 2 Which of these throws instead of returning a value?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 5 ~/ 0

    The / operator follows floating-point rules and always has a double to give. Truncating division must return an int, and no int stands for infinity, so 5 ~/ 0 throws.

A double is a binary fraction, so count money in paise

A double is a 64-bit floating-point number in the IEEE 754 format, the same on every target Dart compiles to. It holds a binary fraction, and most decimal fractions, 0.1 among them, never end when written in base 2, the way 1/3 never ends in base 10. Each is stored as the nearest double, and the small gaps show up in sums and rounding:

floats.dart: sums, rounding and money Dart · floats.dart
// A double is a binary fraction: what that does to sums, rounding and money.
void main() {
  print('0.1 + 0.2 = ${0.1 + 0.2}');
  print('0.1 + 0.2 == 0.3: ${0.1 + 0.2 == 0.3}');
  print('close enough: ${(0.1 + 0.2 - 0.3).abs() < 1e-9}');

  for (final x in [2.5, -2.5, 2.4, -2.6]) {
    print('$x: round ${x.round()}, floor ${x.floor()}, ceil ${x.ceil()}, truncate ${x.truncate()}');
  }
  print('2.675 to 2 places: ${2.675.toStringAsFixed(2)}, 2.665: ${2.665.toStringAsFixed(2)}');
  print('2.675 is stored as ${2.675.toStringAsPrecision(20)}');

  // Ten items of 10 paise each, as rupees in a double and as paise in an int.
  var rupees = 0.0;
  var paise = 0;
  for (var i = 0; i < 10; i++) {
    rupees += 0.1;
    paise += 10;
  }
  print('as double: $rupees, == 1.0: ${rupees == 1.0}');
  print('as int: $paise paise, == 100: ${paise == 100}');

  // 19.99 rupees to paise: toInt() cuts off the fraction, round() takes the nearest.
  print('19.99 * 100 = ${19.99 * 100}');
  print('toInt(): ${(19.99 * 100).toInt()}, round(): ${(19.99 * 100).round()}');
}

Output

0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 == 0.3: false
close enough: true
2.5: round 3, floor 2, ceil 3, truncate 2
-2.5: round -3, floor -3, ceil -2, truncate -2
2.4: round 2, floor 2, ceil 3, truncate 2
-2.6: round -3, floor -3, ceil -2, truncate -2
2.675 to 2 places: 2.67, 2.665: 2.67
2.675 is stored as 2.6749999999999998224
as double: 0.9999999999999999, == 1.0: false
as int: 100 paise, == 100: true
19.99 * 100 = 1998.9999999999998
toInt(): 1998, round(): 1999

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run floats.dart

0.1 + 0.2 printed 0.30000000000000004, so it is not == to 0.3. When “close enough” is the real question, compare the difference with a tolerance, as the third line does. Rounding has two traps of its own:

  • round() sends a half away from zero, so 2.5 became 3 and -2.5 became -3, while floor() always goes down, ceil() always goes up and truncate() goes toward zero.
  • toStringAsFixed(2) rounds the value that is stored, not the one you typed. 2.675 is stored as 2.6749999999999998224, so it printed 2.67, and so did 2.665.

double precision: The 64-bit IEEE 754 floating-point format of Dart's double, with 53 bits of precision; most decimal fractions, such as 0.1, are stored as the nearest binary fraction. is plenty for a length or a temperature, and the wrong tool for money. The last lines of the program show the usual alternative: count money in its smallest unit, as an int. Ten items of 10 paise, added as 0.1 rupees in a double, made 0.9999999999999999, which is not 1.0; added as the int 10, they made exactly 100. Convert a typed amount once, with round(). 19.99 × 100 is 1998.9999999999998 as a double, so toInt(), which cuts the fraction off, gave 1998, one paisa short, while round() gave 1999.

Type 0.1 or 2.675 into the IEEE 754 converter to see the exact decimal value of the double that is stored for each.

Key idea

Keep an amount of money as an int count of its smallest unit, add and compare those ints, and put the decimal point back only when you display the amount.

Checkpoint

Check: Doubles, rounding and money

  1. Question 1 of 2 What do (2.5).round() and (-2.5).round() return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 3 and -3

    The API documentation says round() rounds away from zero when there is no closest integer: 3.5 to 4 and -3.5 to -4, and 2.5 to 3 and -2.5 to -3.

  2. Question 2 of 2 A price arrives as the text 19.99 and you store paise in an int. Which conversion gives 1999?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: (double.parse('19.99') * 100).round()

    A double near a whole number of paise may sit just below it. round() goes to the nearest one, and doing the conversion once, at the edge of the program, keeps every sum after it exact.

Text becomes a number through parse or tryParse

Numbers from a file, a form or the command line arrive as text. int.parse and double.parse turn text into a number and throw a FormatException when they cannot; int.tryParse and double.tryParse return null instead, which suits input that a person typed. This program tries both on good and bad text, and then turns numbers back into text:

parse.dart: from text to numbers and back Dart · parse.dart
// Turning text into numbers, and numbers back into text.
void main() {
  print(int.parse('42') + 1);
  print(int.parse('0x1F')); // a 0x prefix reads hexadecimal
  print(int.parse('-0x1F')); // after an optional sign
  print(int.parse('ff', radix: 16)); // or name the base
  print(int.parse('1010', radix: 2));
  print(int.tryParse('12a')); // null instead of an exception
  print(int.tryParse(' 7 ')); // spaces around the digits are allowed
  print(int.tryParse('1_000')); // separators work in source code, not in text
  print(double.parse('1e3'));
  print(double.tryParse('3,5')); // a comma is not a decimal point
  print(num.parse('8').runtimeType); // num.parse picks int or double

  try {
    int.parse('4.0');
  } on FormatException catch (e) {
    print('FormatException: ${e.message} "${e.source}"');
  }

  const population = 1_000_000; // digit separators in a literal
  const mask = 0xFF_FF;
  print('$population $mask');
  print(255.toRadixString(2)); // back to text in base 2
  print(7.toString().padLeft(3, '0'));
}

Output

43
31
-31
255
10
null
7
null
1000.0
null
int
FormatException: Invalid radix-10 number "4.0"
1000000 65535
11111111
007

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run parse.dart

int.parse read a 0x prefix as hexadecimal, so ‘0x1F’ gave 31, after an optional sign too, and digits of any base from 2 to 36 with radix. Spaces around the digits were accepted, but a letter, a comma or an underscore was not: ‘12a’, ‘3,5’ and ‘1_000’ each gave null, and ‘4.0’ threw “Invalid radix-10 number”, since an int has no decimal point. num.parse('8') returned an int. Going the other way, toRadixString(2) wrote 255 in base 2 and padLeft(3, '0') padded 7 to 007.

The underscores that int.tryParse refused are welcome in source code. Since language 3.6 a number literal may hold digit separator: An underscore between two digits of a number literal, such as 1_000_000, allowed from language version 3.6. It makes a long literal easier to read and does not change its value., one or more underscores between two digits, and they change nothing about the value: 1_000_000 is one million and 0xFF_FF is 65535. A separator anywhere else is an error:

bad_separator.dart: a separator at the end of a literal Dart · bad_separator.dart
// A separator must sit between two digits.
void main() {
  const fee = 1_500_;
  print(fee);
}

Output (exit status 254)

Printed as an error (standard error)

bad_separator.dart:3:15: Error: Digit separators ('_') in a number literal can only be placed between two digits.
Try removing the '_'.
  const fee = 1_500_;
              ^

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run bad_separator.dart

The compiler refused 1_500_ because the last underscore has no digit after it. Separators need language version 3.6, so a package whose SDK constraint: The environment sdk entry of pubspec.yaml, such as ^3.13.0: the Dart SDK versions a package works with. Its lower bound also sets the package's language version. More in “What Dart is and where it runs” starts below 3.6 cannot use them.

Lakh and crore grouping in a literal

Separators may follow Indian grouping too: 1_00_000 is one lakh and 1_00_00_000 is one crore, which reads at a glance in code about rupee amounts. The next program prints both and checks that a crore is ten million.

separators.dart: Indian and international grouping Dart · separators.dart
// Digit separators group a literal however its readers group it (language 3.6 or later).
void main() {
  const lakh = 1_00_000; // Indian grouping
  const crore = 1_00_00_000;
  const million = 1_000_000; // international grouping
  print('$lakh $crore $million');
  print(crore == 10 * million); // the underscores change nothing
}

Output

100000 10000000 1000000
true

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run separators.dart

The run printed plain digits and true: once the program runs, the underscores have left no trace.

Checkpoint

Check: Parsing and digit separators

  1. Question 1 of 2 What does int.tryParse('12a') return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: null

    parse throws a FormatException for text that is not a number, and tryParse returns null for it, which is easier to handle for text a person typed.

  2. Question 2 of 2 Which two of these compile at language version 3.6 or later?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • const n = 1_00_000;
    • const big = 100__000;

    The changelog of Dart 3.6 says separators are not allowed at the start or the end of a number, or next to another character in it such as the decimal point, the x or the e of an exponent.

BigInt is exact at any size, and costs more

When a whole number can pass 2^63 - 1, an int wraps, as the doubling did. BigInt: Dart's integer type of any size, limited only by memory and exact on every target, JavaScript included; it is bigger and slower than int, and its arithmetic takes only other BigInts. is the arbitrary-precision integer of dart:core: its documentation says a big integer can have as many digits as memory allows. This program multiplies 1 × 2 × … × 25 with both:

factorial.dart: 25! as an int and as a BigInt Dart · factorial.dart
// 25! with int and with BigInt.
void main() {
  var asInt = 1;
  var asBig = BigInt.one;
  for (var n = 1; n <= 25; n++) {
    asInt *= n;
    asBig *= BigInt.from(n);
    if (n == 20 || n == 21 || n == 25) print('$n!  int: $asInt  BigInt: $asBig');
  }
  print('fits in an int: ${asBig.isValidInt}');

  final parsed = BigInt.parse('15511210043330985984000000');
  print('same as parsed: ${parsed == asBig}');
  print('2^100 = ${BigInt.two.pow(100)}');
  print('back to int: ${BigInt.from(1 << 40).toInt()}, as double: ${asBig.toDouble()}');
}

Output

20!  int: 2432902008176640000  BigInt: 2432902008176640000
21!  int: -4249290049419214848  BigInt: 51090942171709440000
25!  int: 7034535277573963776  BigInt: 15511210043330985984000000
fits in an int: false
same as parsed: true
2^100 = 1267650600228229401496703205376
back to int: 1099511627776, as double: 1.5511210043330986e+25

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run factorial.dart

The two agreed up to 20!. At 21! the int had already wrapped to a negative number, and 25! as an int printed 7034535277573963776, a wrong number of plausible size, while the BigInt printed all 26 digits of 15511210043330985984000000. A BigInt’s operators take another BigInt, so an int joins in through BigInt.from(n); BigInt.parse reads text, and toInt() and toDouble() convert back. Check isValidInt before toInt(): a value too big for an int is clamped to the largest one, 9223372036854775807, without an error. The price is speed and size: the Numbers in Dart page warns that BigInt and the fixnum package “often result in significantly bigger and slower code”, so keep BigInt for values that really grow that far.

Every number type is now on the page. Answer the four questions for a value from your own code, such as a count of page views, a temperature or the price of an order, and see which types the table rules out:

Interactive · answer and compare Which number type fits your value? Answer four questions about the value you need to store; the table marks the types that fit and says why.
Is the value always a whole number?
Is it an amount of money?
Can it grow past 2^53, about 9 followed by 15 zeros?
Will the same code run in a build compiled to JavaScript?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Dart's number types, and an int of paise for money, against what a value can need
Type Exact whole numbers Fractions Past 2^53 in JavaScript Sums of money Size and speed
int Holds: whole numbers Good exact up to 2^63 - 1 on the VM, then it wraps: 25! became 7034535277573963776 Poor none: int perTable = 9 / 2 does not compile Poor a JavaScript build keeps 53 bits: 2^53 + 3 printed as ...992 Mixed exact for whole rupees, but most prices have paise Good a signed 64-bit integer on native targets
int of paise Holds: money, in its smallest unit Good every amount is a whole number of paise Mixed two decimal places by design: 19.99 is stored as 1999 Mixed exact up to 2^53 paise in JavaScript, far above everyday sums Good sums stay exact: ten items of 10 paise made exactly 100 Good plain int arithmetic
double Holds: binary fractions, 64 bits Mixed exact only up to 2^53; 25! came out as 1.5511210043330986e+25 Good measurements and ratios: 0.25, 1.42e5, sqrt(2) Poor rounds past 2^53 on every target, not only in JavaScript Poor ten 0.1s made 0.9999999999999999, and 0.1 + 0.2 is not 0.3 Good the same 64-bit floating point on every target
num Holds: an int or a double Mixed exact only while it holds an int Good can hold a double Poor it holds an int or a double, so no help past 2^53 Poor as int or double, it brings the problems of whichever it holds Good an int or a double underneath
BigInt Holds: whole numbers of any size Good exact at any size: 25! is 15511210043330985984000000 Poor whole numbers only Good exact on every target, JavaScript included Mixed exact, but heavier than an int of paise for everyday sums Poor Dart warns of bigger and slower code; it cannot mix with int
How the answers decide
  • “Is it an amount of money?” Yes: rules out double and num. Binary fractions cannot hold most amounts with paise exactly, so their sums drift; ten 0.1s made 0.9999999999999999.
  • “Is it an amount of money?” Yes and “Is the value always a whole number?” No, like a length or a ratio: rules out int and adds a caveat to int of paise. Store the amount as a whole number of paise: 19.99 becomes 1999, and only display code puts the decimal point back.
  • “Is the value always a whole number?” No, like a length or a ratio and “Is it an amount of money?” No: rules out int, int of paise and BigInt. A length, a ratio or a measurement needs a fraction, which only double, or num holding one, can carry.
  • “Is the value always a whole number?” Yes, like a count and “Can it grow past 2^53, about 9 followed by 15 zeros?” Yes, it can and “Will the same code run in a build compiled to JavaScript?” Yes, on the web too: rules out int, int of paise, double and num. A JavaScript build keeps 53 bits for every int, so only BigInt stays exact past 2^53 there.
  • “Is the value always a whole number?” Yes, like a count and “Can it grow past 2^53, about 9 followed by 15 zeros?” Yes, it can and “Will the same code run in a build compiled to JavaScript?” No, native or WebAssembly only: rules out double and num and adds a caveat to int. A native or WebAssembly int is exact up to 2^63 - 1 and then wraps without an error; past that, use BigInt.
  • “Is the value always a whole number?” Yes, like a count and “Can it grow past 2^53, about 9 followed by 15 zeros?” No, it stays far below: adds a caveat to BigInt and double. An int is exact and fast for this value; BigInt would cost size and speed for nothing, and a double hides that the value is whole.

For most values the answer is short: an int for a count, a double for a measurement, an int of paise for money, and BigInt only past 2^63 - 1, or past 2^53 when the code also runs as JavaScript, which the next section shows.

Checkpoint

Check: BigInt and choosing a type

  1. Question 1 of 2 Which of these values needs BigInt on the Dart VM?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The product 1 × 2 × … × 25

    An int wraps past 2^63 - 1 on the VM. BigInt is exact at any size, at a cost in speed and code size, so it is for the values that really grow that far.

  2. Question 2 of 2 A wallet app keeps each balance in rupees and paise, and the same code runs on phones and in a JavaScript build. Which type fits the balance best?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An int that counts paise

    Money is whole numbers of its smallest unit. An int of paise is exact on every target up to 2^53 paise, and only the code that displays a balance puts a decimal point back.

A JavaScript build keeps 53 bits, a WebAssembly build keeps 64

The first lesson of this track compiled one program three ways and saw 2^53 + 1 print as 2^53 in JavaScript. The reason is what each compiler turns an int into. JavaScript has a single kind of number, a 64-bit double, and dart2js: The Dart compiler behind dart compile js, which turns a program into JavaScript. There int and double are both JavaScript numbers, so integers are exact only up to 2^53., the compiler behind dart compile js, maps both int and double to it, which leaves 53 bits of integer precision. dart2wasm: The Dart compiler behind dart compile wasm, which turns a program into a WebAssembly module that uses WasmGC, plus a small JavaScript file that loads it., behind dart compile wasm, printed the VM’s 64-bit results in every run of this lesson. Step through a counter that starts at 2^53 on each target:

Interactive · step by step A counter past 2^53 on three targets Step through what each target stores when the counter of web/numbers.dart adds 1 three times to 2^53.
A counter past 2^53 on three targets, step 6 of 6. Showing count = 2^53, count += 1, Dart VM (64-bit int: ...995), JavaScript build (bits: only the low 32), WebAssembly build (64-bit int: ...995). In focus: JavaScript build.count = 2^53count += 1Dart VM64-bit int: ...995JavaScript buildbits: only the low 32WebAssembly build64-bit int: ...995

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The starting value. count starts at 9007199254740992, which is 2 to the power 53. Every target stores this number exactly, because it is a power of two.
  2. Adding 1 on the VM. On the Dart VM an int is a 64-bit two's-complement integer, 63 bits for the value and one for the sign, so 2^53 + 1 is just another exact value.
  3. Adding 1 in JavaScript. A JavaScript build stores every int as a 64-bit double, which has 53 bits of precision. 2^53 + 1 would need one bit more, so the sum is rounded to a double it can hold, which is 2^53 again.
  4. Stuck without an error. The second and third + 1 round back the same way. After three additions the VM holds 9007199254740995 and the JavaScript build still prints 9007199254740992, and neither run reported a problem.
  5. The WebAssembly build. dart2wasm keeps an int as a 64-bit integer, as the VM does, so the WebAssembly build printed 9007199254740995 too.
  6. Bits in JavaScript. Bit operations differ too. A JavaScript build works on the low 32 bits only, so 1 << 32 gave 0 and (-1).oneBitCount gave 32, where the VM and the WebAssembly build gave 4294967296 and 64.

The program behind the figure prints the counter and nine more lines of results:

web/numbers.dart: expressions that depend on the target Dart · web/numbers.dart
import 'dart:math';

// The same expressions on the Dart VM, as JavaScript and as WebAssembly.
void main() {
  var count = 9007199254740992; // 2^53
  for (var i = 0; i < 3; i++) {
    count += 1;
  }
  print('2^53 plus 1, three times: $count');

  var doubled = 1;
  var factorial = 1;
  for (var i = 1; i <= 63; i++) {
    doubled *= 2;
    if (i <= 25) factorial *= i;
  }
  print('1 doubled 63 times: $doubled');
  print('25! as an int: $factorial');

  var shift = 32;
  print('1 << 32: ${1 << shift}, 1 << 40: ${1 << 40}');
  print('(-1).oneBitCount: ${(-1).oneBitCount}');
  print('(1 << 40).trailingZeroBitCount: ${(1 << 40).trailingZeroBitCount}');

  Object one = 1;
  print('1 is double: ${one is double}, identical(1, 1.0): ${identical(one, 1.0)}');
  print('10.0 * 2 prints as ${10.0 * 2}');
  print('-7 % 3 = ${-7 % 3}, 0.1 + 0.2 = ${0.1 + 0.2}');

  final r = Random(42);
  print('Random(42): ${[for (var i = 0; i < 5; i++) r.nextInt(100)]}');
}

The session runs it on the VM, compiles it to JavaScript and to WebAssembly, and runs both builds with Node.js 24.21.0, with a small loader for the WebAssembly module like the first lesson’s:

numbers.dart on the VM, as JavaScript and as WebAssembly Python · web_session.py
from session import files, run

# web/numbers.dart on the Dart VM, compiled to JavaScript and compiled to WebAssembly (both run by Node.js).
files("web/numbers.dart=numbers.dart", "web/run_wasm.mjs=run_wasm.mjs")
run(
    """
dart run numbers.dart
dart compile js -O2 -o out/numbers.js numbers.dart > /dev/null
node out/numbers.js
dart compile wasm -O2 -o out/numbers.wasm numbers.dart > /dev/null
node run_wasm.mjs
"""
)

Output

$ dart run numbers.dart
2^53 plus 1, three times: 9007199254740995
1 doubled 63 times: -9223372036854775808
25! as an int: 7034535277573963776
1 << 32: 4294967296, 1 << 40: 1099511627776
(-1).oneBitCount: 64
(1 << 40).trailingZeroBitCount: 40
1 is double: false, identical(1, 1.0): false
10.0 * 2 prints as 20.0
-7 % 3 = 2, 0.1 + 0.2 = 0.30000000000000004
Random(42): [87, 58, 4, 85, 77]
$ dart compile js -O2 -o out/numbers.js numbers.dart > /dev/null
$ node out/numbers.js
2^53 plus 1, three times: 9007199254740992
1 doubled 63 times: 9223372036854776000
25! as an int: 1.5511210043330986e+25
1 << 32: 0, 1 << 40: 0
(-1).oneBitCount: 32
(1 << 40).trailingZeroBitCount: 32
1 is double: true, identical(1, 1.0): true
10.0 * 2 prints as 20
-7 % 3 = 2, 0.1 + 0.2 = 0.30000000000000004
Random(42): [87, 58, 4, 85, 77]
$ dart compile wasm -O2 -o out/numbers.wasm numbers.dart > /dev/null
$ node run_wasm.mjs
2^53 plus 1, three times: 9007199254740995
1 doubled 63 times: -9223372036854775808
25! as an int: 7034535277573963776
1 << 32: 4294967296, 1 << 40: 1099511627776
(-1).oneBitCount: 64
(1 << 40).trailingZeroBitCount: 40
1 is double: false, identical(1, 1.0): false
10.0 * 2 prints as 20.0
-7 % 3 = 2, 0.1 + 0.2 = 0.30000000000000004
Random(42): [87, 58, 4, 85, 77]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 web_session.py

The VM and the WebAssembly build printed the same ten lines. The JavaScript build differed in eight of them:

  • The counter stayed at 9007199254740992, and 1 doubled 63 times printed 9223372036854776000, a rounded positive number where the other two printed the wrapped -9223372036854775808.
  • 25! as an int printed 1.5511210043330986e+25: no wrap, but a double with 17 significant digits in place of the 26 exact ones.
  • Bit operations used the low 32 bits only: 1 << 32 and 1 << 40 gave 0, (-1).oneBitCount gave 32 and (1 << 40).trailingZeroBitCount gave 32, where the others gave 4294967296, 1099511627776, 64 and 40.
  • 1 counted as a double, identical(1, 1.0) was true, and 10.0 × 2 printed as 20 instead of 20.0.

A literal that JavaScript cannot hold does not even compile. odd.dart stores the odd number 9007199254740993 as a constant:

literal/odd.dart: an odd literal just above 2^53 Dart · literal/odd.dart
// An odd number just above 2^53, written as a literal.
void main() {
  const odd = 9007199254740993;
  print('$odd is odd: ${odd.isOdd}');
}
The literal for the VM, dart2js and dart2wasm Python · literal_session.py
from session import files, run

# A literal above 2^53: the VM runs it, dart2js refuses to compile it, dart2wasm compiles it.
files("literal/odd.dart=odd.dart", "literal/run_wasm.mjs=run_wasm.mjs")
run(
    """
dart run odd.dart
!dart compile js -o out/odd.js odd.dart
dart compile wasm -o out/odd.wasm odd.dart > /dev/null
node run_wasm.mjs
"""
)

Output

$ dart run odd.dart
9007199254740993 is odd: true
$ dart compile js -o out/odd.js odd.dart
odd.dart:3:15:
Error: The integer literal 9007199254740993 can't be represented exactly in JavaScript.
  const odd = 9007199254740993;
              ^^^^^^^^^^^^^^^^
Error: Compilation failed.
[exit status 1]
$ dart compile wasm -o out/odd.wasm odd.dart > /dev/null
$ node run_wasm.mjs
9007199254740993 is odd: true

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 literal_session.py

The VM and the WebAssembly build printed it, while dart2js stopped with “The integer literal 9007199254740993 can’t be represented exactly in JavaScript.” That error is a help, because a value computed while the program runs, like the counter, gets no warning at all.

New in Dart 3.13: oneBitCount and trailingZeroBitCount

Both getters arrived in Dart 3.13. The SDK changelog says they work on the full 64-bit two’s-complement value on native platforms and on the least significant 32 bits on the web, which is what the session printed.

Checkpoint

Check: Numbers in JavaScript and Wasm builds

  1. Question 1 of 2 In a JavaScript build, a counter starts at 2^53 and adds 1 three times. What does it print?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 9007199254740992

    A JavaScript build stores an int as a 64-bit double with 53 bits of precision. Above 2^53 the doubles are 2 apart, so adding 1 rounds back to where the count was.

  2. Question 2 of 2 What did (-1).oneBitCount print in the JavaScript build?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 32 bits

    On the web, oneBitCount uses the least significant 32 bits only. The VM and the WebAssembly build use all 64 bits of -1, which are all ones, and printed 64.

dart test can run the same tests on three targets

A difference that shows only in a JavaScript build is easy to miss, because dart test runs tests on the Dart VM unless you say otherwise. The package counting has two small functions and three tests of them:

lib/counting.dart: a counter and a flag count Dart · matrix/lib/counting.dart
/// Adds 1 to [start], [times] times, as a counter does.
int countUp(int start, int times) {
  var n = start;
  for (var i = 0; i < times; i++) {
    n += 1;
  }
  return n;
}

/// How many flags are set in [mask], one flag per bit.
int flagsSet(int mask) => mask.oneBitCount;
test/counting_test.dart: three tests Dart · matrix/test/counting_test.dart
import 'package:counting/counting.dart';
import 'package:test/test.dart';

void main() {
  test('counts small numbers', () {
    expect(countUp(1, 2), 3);
  });

  test('counts past 2^53', () {
    // Compared as text: a JavaScript build cannot even compile the literal 9007199254740995.
    expect(countUp(9007199254740992, 3).toString(), '9007199254740995');
  });

  test('sees flags 0 and 40', () {
    expect(flagsSet(1 << 40 | 1), 2);
  });
}

The test past 2^53 compares text, because the expected value written as an int, 9007199254740995, would stop dart2js from compiling the test file, and none of its tests would run. The first session runs the tests on the VM, then with -p node, which compiles them with dart2js and runs them in Node.js:

dart test on the VM, then with -p node Python · test_session.py
from session import files, run

# The package's tests on the Dart VM, then compiled to JavaScript and run by Node.js.
files("matrix=counting")
run(
    """
cd counting
dart pub get --offline > /dev/null
dart test
!dart test -p node
""",
    pub=True,
)

Output

$ cd counting
$ dart pub get --offline > /dev/null
$ dart test
--:-- +0: loading test/counting_test.dart
--:-- +0: test/counting_test.dart: counts small numbers
--:-- +1: test/counting_test.dart: counts past 2^53
--:-- +2: test/counting_test.dart: sees flags 0 and 40
--:-- +3: All tests passed!
$ dart test -p node
--:-- +0: loading test/counting_test.dart
--:-- +0: test/counting_test.dart: counts small numbers
--:-- +1: test/counting_test.dart: counts past 2^53
--:-- +1 -1: test/counting_test.dart: counts past 2^53 [E]
  Expected: '9007199254740995'
    Actual: '9007199254740992'
     Which: is different.
            Expected: ... 99254740995
              Actual: ... 99254740992
                                    ^
             Differ at offset 15

  org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart 1424:15   Object.throwExpression
  package:matcher                                                           Object.expect
  test/counting_test.dart 11:5                                              main.<fn>
  package:test_api                                                          <fn>
  org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 339:19  _wrapJsFunctionForAsync.closure.$protected
  org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 365:23  _wrapJsFunctionForAsync.<fn>
  org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 313:7   _awaitOnObject.<fn>
  org-dartlang-sdk:///lib/async/zone_root.dart 48:39                        StaticClosure._rootRunUnary

--:-- +1 -1: test/counting_test.dart: sees flags 0 and 40
--:-- +1 -2: test/counting_test.dart: sees flags 0 and 40 [E]
  Expected: <2>
    Actual: <1>

  org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart 1424:15   Object.throwExpression
  package:matcher                                                           Object.expect
  test/counting_test.dart 15:5                                              main.<fn>
  package:test_api                                                          <fn>
  org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 339:19  _wrapJsFunctionForAsync.closure.$protected
  org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 365:23  _wrapJsFunctionForAsync.<fn>
  org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 313:7   _awaitOnObject.<fn>
  org-dartlang-sdk:///lib/async/zone_root.dart 48:39                        StaticClosure._rootRunUnary

--:-- +1 -2: Some tests failed.

Failing tests:
  test/counting_test.dart: counts past 2^53
  test/counting_test.dart: sees flags 0 and 40

Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'dart test --chain-stack-traces'.
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 test_session.py

All three passed on the VM. Under Node.js the counter test failed with Expected ‘9007199254740995’ and Actual ‘9007199254740992’, and the flag test found 1 flag where it expected 2, because 1 << 40 is 0 in a JavaScript build. The second session adds -c dart2wasm, which compiles the same tests to WebAssembly instead:

dart test with -p node -c dart2wasm Python · test_wasm_session.py
from session import files, run

# The same tests compiled to WebAssembly and run by Node.js.
files("matrix=counting")
run(
    """
cd counting
dart pub get --offline > /dev/null
dart test -p node -c dart2wasm
""",
    pub=True,
)

Output

$ cd counting
$ dart pub get --offline > /dev/null
$ dart test -p node -c dart2wasm
--:-- +0: [Node.js, Dart2Wasm] loading test/counting_test.dart
--:-- +0: [Node.js, Dart2Wasm] test/counting_test.dart: counts small numbers
--:-- +1: [Node.js, Dart2Wasm] test/counting_test.dart: counts past 2^53
--:-- +2: [Node.js, Dart2Wasm] test/counting_test.dart: sees flags 0 and 40
--:-- +3: All tests passed!

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 test_wasm_session.py

All three passed again, each test name now starting with [Node.js, Dart2Wasm]. The package:test documentation adds that one command can list several platforms, as in -p "chrome,vm", and that @TestOn limits a test file to some platforms.

Key idea

Integer arithmetic past 2^53 and bit operations on large values can pass every test on the VM and fail in a JavaScript build, so run such tests with -p node too, and with -c dart2wasm when you ship WebAssembly.

Checkpoint

Check: Tests on three targets

  1. One question Which command runs a package's tests compiled to WebAssembly and run by Node.js?

    Choose one answer.

    Show the answer: One question

    Answer: dart test -p node -c dart2wasm

    dart test runs on the VM by default. -p node compiles the tests to JavaScript for Node.js, and adding -c dart2wasm compiles them to WebAssembly instead.

A seeded Random repeats itself, and Random.secure() keeps secrets

The dart:math library holds the rest of the everyday number tools: min and max, sqrt, pow, constants such as pi and e, and Random, which draws random ints, doubles and bools. This program uses a few of them:

random.dart: dart:math, a seeded Random and a secure one Dart · random.dart
import 'dart:math';

// dart:math's functions, a seeded generator twice, and a secure one.
void main() {
  print('max ${max(3, 8)}, min ${min(-2, 5)}, sqrt ${sqrt(2)}, pow ${pow(2, 10)}, pi $pi');

  List<int> draw(Random r) => [for (var i = 0; i < 5; i++) r.nextInt(100)];
  print('Random(42): ${draw(Random(42))}');
  print('Random(42) again: ${draw(Random(42))}');
  print('Random(7): ${draw(Random(7))}');

  final secure = Random.secure();
  final roll = secure.nextInt(6) + 1; // a different value on every run
  print('secure roll is 1 to 6: ${roll >= 1 && roll <= 6}');
}

Output

max 8, min -2, sqrt 1.4142135623730951, pow 1024, pi 3.141592653589793
Random(42): [87, 58, 4, 85, 77]
Random(42) again: [87, 58, 4, 85, 77]
Random(7): [20, 37, 78, 49, 6]
secure roll is 1 to 6: true

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run random.dart

Random(42) gave 87, 58, 4, 85 and 77 twice, because a seed fixes the whole sequence, which makes a shuffle in a test or a simulation repeatable, and Random(7) gave another five. The web session printed the same five numbers for Random(42) on all three targets, but nothing promises that: the constructor’s documentation says “The implementation of the random stream can change between releases of the library”. So a test may seed a generator and check properties of what it draws, never the exact numbers.

The default generator is not for secrets either. Its documentation describes pseudo-random bits that are “not suitable for cryptographic purposes”, and the seeded runs above show the risk: the same seed gives the same values, so whoever learns or guesses a seed can produce them too. Random.secure() draws from a cryptographically secure source instead, and throws an UnsupportedError on a platform that cannot provide one. Its values change on every run, which is why the program prints only that its roll was from 1 to 6.

Pick the generator by what a guess would cost

A game, a shuffle or a test may use Random() or Random(seed). A session token, a password-reset link or anything else an attacker gains by guessing needs Random.secure().

Checkpoint

Check: Seeded and secure random numbers

  1. Question 1 of 2 A test shuffles a list with Random(42) and checks that the result is exactly [3, 1, 2]. What is wrong with it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Nothing promises that Random(42) gives the same sequence after an SDK upgrade

    A seed makes a run repeatable with one implementation of Random. Check properties of the result, such as the same items in some order, rather than one exact sequence.

  2. Question 2 of 2 Which generator should make a password-reset token?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Random.secure()

    The Random class documentation says to use the Random.secure constructor for cryptographic purposes; it throws an UnsupportedError on a platform that has no secure source.

Practise: format and parse amounts in paise

Money code is where this lesson’s rules meet, so build the two functions a checkout screen needs, with no double anywhere:

  1. Write String formatPaise(int paise) with ~/, % and padLeft: the whole rupees with Indian digit grouping, a decimal point and exactly two digits of paise.
  2. Write int parseRupees(String s) that accepts the grouping commas, an optional rupee sign and one or two decimals, returns paise, and throws a FormatException for text such as ‘1.234’ or ‘12a’.
  3. Put both in lib/ of a package, write five tests, and run dart test, dart test -p node and dart test -p node -c dart2wasm.
  4. Add a test that formats 9007199254740993 paise, an amount above 2^53, and note which of the three runs fails, and why.

The amounts to test with

formatPaise(12345678) returns ‘₹1,23,456.78’ and formatPaise(5) returns ‘₹0.05’. parseRupees('1,23,456.78') returns 12345678, parseRupees('₹99') returns 9900 and parseRupees('0.5') returns 50. Indian grouping puts a comma after the last three digits of the rupees, then after every two: lakh and crore.

Then answer the self-check. It asks about the idea you know least first, and explains every answer at once:

Practice that adapts to you

Practise this lesson

20 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 20 What is the type of 2.5 + 1?

    Choose one answer.

    Show the answer: Question 1 of 20

    Answer: double

    int and double are both subtypes of num. Adding an int to a double gives a double, so 2.5 + 1 is 3.5.

  2. Question 2 of 20 On the Dart VM, 1 == 1.0 is true but identical(1, 1.0) is false. Why?

    Choose one answer.

    Show the answer: Question 2 of 20

    Answer: They are equal numbers, but one is an int object and the other a double object

    On native platforms no value can be both an int and a double. In a JavaScript build both are the same kind of number, which is why the web session printed true for identical(1, 1.0).

  3. Question 3 of 20 What does 9223372036854775807 + 1 give on the Dart VM?

    Choose one answer.

    Show the answer: Question 3 of 20

    Answer: -9223372036854775808

    2^63 - 1 is the largest native int. Adding 1 carries into the sign bit, which gives -2^63, the smallest.

  4. Question 4 of 20 A function multiplies the numbers 1 to 21 into an int and returns -4249290049419214848. What does the negative result tell you?

    Choose one answer.

    Show the answer: Question 4 of 20

    Answer: The product passed 2^63 - 1 and wrapped, so the int result is wrong

    A negative product of positive numbers is a sign of overflow. Use BigInt, which printed 51090942171709440000 for 21!, or check that the operands keep the product below 2^63.

  5. Question 5 of 20 What does -7 ~/ 2 give?

    Choose one answer.

    Show the answer: Question 5 of 20

    Answer: -3

    Truncating division drops the fraction of the exact quotient, so for negative numbers it rounds up toward zero.

  6. Question 6 of 20 Five players sit in a ring, numbered 0 to 4, and the turn passes backwards from player 0. Which expression gives the next player, 4?

    Choose one answer.

    Show the answer: Question 6 of 20

    Answer: (0 - 1) % 5

    Positions that wrap around want a result from 0 to n - 1 for any input, which is exactly what % gives, as the week in divide.dart showed with (2 - 10) % 7.

  7. Question 7 of 20 Why is 0.1 + 0.2 == 0.3 false in Dart?

    Choose one answer.

    Show the answer: Question 7 of 20

    Answer: 0.1, 0.2 and 0.3 have no exact binary form, and the sum lands on a different double than 0.3

    A double is a binary fraction. Compare doubles with a tolerance when close enough is the question, and keep money in ints so that the question never comes up.

  8. Question 8 of 20 What does 2.675.toStringAsFixed(2) return?

    Choose one answer.

    Show the answer: Question 8 of 20

    Answer: '2.67'

    toStringAsFixed rounds the stored binary value, not the literal you typed. floats.dart printed 2.67 for both 2.675 and 2.665.

  9. Question 9 of 20 An int p holds an amount in paise. Which expression shows it with exactly two digits of paise, 10.05 for 1005 and 10.50 for 1050?

    Choose one answer.

    Show the answer: Question 9 of 20

    Answer: '${p ~/ 100}.${(p % 100).toString().padLeft(2, '0')}'

    Integer division and the modulo split an amount into its two parts exactly, and padding the paise to two digits is the display step that the money section saves for last.

  10. Question 10 of 20 What does int.parse('0x1F') return?

    Choose one answer.

    Show the answer: Question 10 of 20

    Answer: 31

    Without a radix, int.parse reads a 0x prefix as hexadecimal: 1F is 16 + 15, which is 31. A sign may come before the prefix: parse.dart printed -31 for '-0x1F'.

  11. Question 11 of 20 A settings file contains the text 1_000_000. What does int.tryParse return for it?

    Choose one answer.

    Show the answer: Question 11 of 20

    Answer: null, because digit separators are part of Dart's source code, not of the number formats that parse reads

    Separators are syntax for people reading code. Text from outside the program has its own format, so remove the underscores (or commas) before parsing, as the practice task does with grouping commas.

  12. Question 12 of 20 Which of these expressions compiles?

    Choose one answer.

    Show the answer: Question 12 of 20

    Answer: BigInt.from(5) + BigInt.one

    BigInt does not mix with int. Convert first with BigInt.from(n), and convert back with toInt() once isValidInt says the value fits.

  13. Question 13 of 20 A BigInt result must be passed to a function that takes an int. How do you check first that no digits will be lost?

    Choose one answer.

    Show the answer: Question 13 of 20

    Answer: Read its isValidInt property

    isValidInt is true when the value fits an int on the platform. factorial.dart printed false for 25!, whose 26 digits no int can hold.

  14. Question 14 of 20 What does dart compile js do with const odd = 9007199254740993; in a program?

    Choose one answer.

    Show the answer: Question 14 of 20

    Answer: It stops with an error, because the literal cannot be represented exactly in JavaScript

    dart2js refuses integer literals that a JavaScript number cannot hold. The VM and dart2wasm compiled the same file and printed 9007199254740993.

  15. Question 15 of 20 A check compares the text of 10.0 * 2 inside a message with '20.0 cows'. Where does it fail?

    Choose one answer.

    Show the answer: Question 15 of 20

    Answer: In a JavaScript build, where 10.0 * 2 prints as 20

    The Numbers in Dart page suggests building the expected text from the same expression, as in the string 20.0 written through interpolation, so that both sides format the number the same way on every target.

  16. Question 16 of 20 Code packs two 32-bit numbers into one int with (high << 32) | low. Where does it break, and why?

    Choose one answer.

    Show the answer: Question 16 of 20

    Answer: In a JavaScript build, because bit operations there work on 32 bits and high << 32 gives 0

    For bit work that must agree on every target, the Numbers in Dart page suggests working on 32-bit chunks, with toSigned(32) where a chunk must be read as signed.

  17. Question 17 of 20 Where does dart test run a package's tests when you give no platform?

    Choose one answer.

    Show the answer: Question 17 of 20

    Answer: On the Dart VM

    A JavaScript-only difference stays hidden while the tests run on the VM alone, which is why the lesson ran the same tests again with -p node and with -c dart2wasm.

  18. Question 18 of 20 Why did counting_test.dart compare the counter's result as text, '9007199254740995', instead of with the int literal 9007199254740995?

    Choose one answer.

    Show the answer: Question 18 of 20

    Answer: With the literal, dart2js would refuse to compile the test file, so the -p node run would show no test result at all

    Comparing text lets the same test file compile everywhere and fail only where the value is really wrong: Node.js reported Actual '9007199254740992' for the counter.

  19. Question 19 of 20 What does the documentation of the Random constructor promise about the numbers that one seed gives?

    Choose one answer.

    Show the answer: Question 19 of 20

    Answer: Nothing across releases; the implementation of the random stream can change between releases of the library

    A seed makes runs repeatable on one SDK version. Treat the exact numbers as an accident of the implementation, and test what does not depend on them.

  20. Question 20 of 20 What does Random.secure() do on a platform that has no cryptographically secure source of random numbers?

    Choose one answer.

    Show the answer: Question 20 of 20

    Answer: It throws an UnsupportedError

    Failing loudly is the safe choice for secrets: code that needs a secure value learns at once that the platform cannot provide one.

Interview questions

Warm-up (fresher): How do % and remainder() treat a negative number in Dart?

They agree for positive numbers and differ when the number being divided is negative. remainder() belongs to truncating division: a ~/ b rounds toward zero, and a.remainder(b) is what is left, so the result has the sign of a: -7.remainder(3) is -1, because -7 ~/ 3 is -2. The % operator is the Euclidean modulo: its result is always from 0 up to, but not including, the absolute value of b, so -7 % 3 is 2, because -7 equals 3 times -3 plus 2, and 7 % -3 is 1. Use % for anything that wraps around, such as a weekday counted backwards or an index in a ring buffer, and remainder() when the result must keep the sign of the original number.

Two longer questions in the module’s bank build on the money and randomness parts of this lesson:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A bill-splitting screen divides 1,000 rupees among 3 friends. It computes 1000 / 3, shows 333.33 for each person, and the three shares add up to 999.99, one paisa short of the bill. A 5 % service charge is coming next. How do you compute every share so the shares always add up to the bill?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A sign-in screen texts a 6-digit code. The code is made with Random(DateTime.now().millisecondsSinceEpoch).nextInt(999999), stored as an int, and compared with int.parse of what the person types. Name every problem you see, and say what you would ship instead.

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What do int, double and num hold, and what does / return?

    int holds whole numbers, double binary fractions, and num is the type above both. / always returns a double, so a whole-number result comes from ~/ or from rounding.

  2. What happens when an int on the Dart VM passes 2^63 - 1?

    It wraps around without an error: on the VM and in native builds an int is a 64-bit two's-complement integer, so doubling 1 sixty-three times printed -9223372036854775808.

  3. What do -7 % 3 and (-7).remainder(3) return?

    2 and -1. % is the Euclidean modulo, never below 0, while remainder() keeps the sign of the number divided, because -7 ~/ 3 is -2 (~/ rounds toward zero).

  4. Stepping back from the first item of a list, why write (i - 1) % length?

    (i - 1).remainder(length) gives -1, and a list has no position -1. (i - 1) % length gives length - 1, the last position, which a carousel or a ring of players needs.

  5. How should a Dart program keep an amount of money?

    As an int count of the smallest unit, such as paise, because a double cannot hold most decimal fractions exactly. Add and compare the ints, convert typed text once with round(), and put the decimal point back only for display.

  6. What does int.tryParse return for text that is not a number?

    null. Digit separators such as 1_000 work only in literals in the code, not in the text you parse.

  7. When is BigInt worth its cost?

    When a whole number can grow past the 64 bits of an int: BigInt stays exact at any size, at a cost in speed and code size.

  8. How do integers differ in a JavaScript build and a WebAssembly build?

    A JavaScript build keeps 53 bits of integer precision and does bit operations on 32 bits; a WebAssembly build keeps 64 bits. dart test -p node, with and without -c dart2wasm, shows the difference before users do.

Key takeaways

  • An int holds whole numbers and a double binary fractions, num is the type above both, and / always returns a double, so integer results come from ~/ or from rounding.
  • On the VM and in native builds an int is a 64-bit two’s-complement integer that wraps past 2^63 - 1 without an error, which is why doubling 1 sixty-three times printed -9223372036854775808.
  • ~/ rounds toward zero and remainder() keeps the sign of the number divided, while % never goes below 0, which made (2 - 10) % 7 the Sunday that remainder() could not give.
  • A double cannot hold most decimal fractions exactly, so amounts of money are counted as an int of paise and converted from typed text once, with round().
  • int.tryParse returns null for text that is not a number, digit separators work only in literals, and BigInt stays exact at any size at a cost in speed and code size.
  • A JavaScript build keeps 53 bits of integer precision and 32-bit bit operations, a WebAssembly build keeps 64 bits, and dart test -p node with and without -c dart2wasm shows the difference before users do.

Terms in this lesson

BigInt
Dart's integer type of any size, limited only by memory and exact on every target, JavaScript included; it is bigger and slower than int, and its arithmetic takes only other BigInts.
dart2js
The Dart compiler behind dart compile js, which turns a program into JavaScript. There int and double are both JavaScript numbers, so integers are exact only up to 2^53.
dart2wasm
The Dart compiler behind dart compile wasm, which turns a program into a WebAssembly module that uses WasmGC, plus a small JavaScript file that loads it.
digit separator
An underscore between two digits of a number literal, such as 1_000_000, allowed from language version 3.6. It makes a long literal easier to read and does not change its value.
double precision
The 64-bit IEEE 754 floating-point format of Dart's double, with 53 bits of precision; most decimal fractions, such as 0.1, are stored as the nearest binary fraction.
Euclidean modulo
The remainder that Dart's % operator returns: always from 0 up to, but not including, the absolute value of the divisor, so -7 % 3 is 2 while -7.remainder(3) is -1.
integer overflow
A whole-number result too large for its type. On native platforms a Dart int that passes 2^63 - 1 wraps around to a negative number, with no exception and no warning.
two's complement
How a native Dart int stores whole numbers in 64 bits: the top bit counts as minus 2^63, so the range runs from -2^63 to 2^63 - 1, and a result past either end wraps around to the other.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.