ZetCode

Dart BigInt

last modified May 30, 2026

In Dart, BigInt is a data type that represents arbitrary-precision integers, allowing developers to work with numbers larger than 64 bits. Unlike standard int, which has a fixed size limit based on the system architecture, BigInt can store extremely large values without overflow, making it essential for applications requiring high-precision calculations.

BigInt is particularly useful in domains where large numerical values are common, such as:

BigInt is part of Dart's core library, so it requires no additional dependencies. The class provides a rich set of operations including:

By leveraging BigInt, Dart developers can perform precise calculations without integer overflow, making it a crucial tool for cryptography, scientific computing, and blockchain applications.

Creating BigInt Values

BigInt values can be created in several ways: from an integer literal with BigInt.from, by parsing a decimal string with BigInt.parse, or by using the predefined constants BigInt.zero, BigInt.one, and BigInt.two.

main.dart
void main() {
  var big1 = BigInt.from(123456789);
  var big2 = BigInt.parse('98765432109876543210');
  var big3 = BigInt.zero;
  var big4 = BigInt.one;
  var big5 = BigInt.two;

  print(big1);
  print(big2);
  print(big3);
  print(big4);
  print(big5);
}

BigInt.from converts a Dart int to BigInt, while BigInt.parse accepts any decimal string regardless of size. The constants BigInt.zero, BigInt.one, and BigInt.two are convenient pre-built values for common initial states such as accumulators or counters.

$ dart main.dart
123456789
98765432109876543210
0
1
2

Parsing with Radix

BigInt.parse accepts an optional radix parameter, allowing values to be parsed from hexadecimal, binary, octal, or any other base between 2 and 36. The toString method similarly accepts a radix for formatting output.

main.dart
void main() {
  var fromHex = BigInt.parse('DEADBEEFCAFE', radix: 16);
  var fromBin = BigInt.parse('11001010111111101101010', radix: 2);
  var fromOct = BigInt.parse('755', radix: 8);

  print('Hex -> decimal: $fromHex');
  print('Binary -> decimal: $fromBin');
  print('Octal -> decimal: $fromOct');

  var n = BigInt.parse('123456789012345678901234567890');
  print('Decimal: $n');
  print('Hex: ${n.toRadixString(16)}');
  print('Binary: ${n.toRadixString(2)}');
  print('Base 36: ${n.toRadixString(36)}');
}

When radix is omitted, the default is 10 (decimal). Hex input must not include the 0x prefix; strip it first if necessary. toRadixString is a convenient shorthand for toString(radix: …).

$ dart main.dart
Hex -> decimal: 244838016843006
Binary -> decimal: 6647146
Octal -> decimal: 493
Decimal: 123456789012345678901234567890
Hex: 5ce0d6380b2a6ea3cb43c303d2
Binary: 101110011100000110101100011100000001011001010100110111010100011110010110100001111000011000000111101 0010
Base 36: 9ugzjoynhxzflyxynqmei

Converting BigInt

BigInt can be converted to int, double, or String. When converting to int, values that exceed the platform's integer range are truncated, so always check isValidInt first.

main.dart
void main() {
  var small = BigInt.from(42);
  var huge = BigInt.parse('99999999999999999999999999999999');

  // BigInt -> int
  print('small.isValidInt: ${small.isValidInt}');
  print('small.toInt(): ${small.toInt()}');

  print('huge.isValidInt: ${huge.isValidInt}');

  // BigInt -> double (loses precision for very large values)
  print('huge.toDouble(): ${huge.toDouble()}');

  // BigInt -> String
  print('small.toString(): ${small.toString()}');
  print('small.toRadixString(2): ${small.toRadixString(2)}');
  print('small.toRadixString(16): ${small.toRadixString(16)}');

  // int -> BigInt
  int n = 999;
  BigInt b = BigInt.from(n);
  print('int to BigInt: $b');
}

isValidInt returns true when the value fits into a 64-bit signed integer (the native Dart VM integer type). For very large BigInt values, toDouble may lose precision because double is a 64-bit floating-point type with only 53 mantissa bits.

$ dart main.dart
small.isValidInt: true
small.toInt(): 42
huge.isValidInt: false
huge.toDouble(): 1e+32
small.toString(): 42
small.toRadixString(2): 101010
small.toRadixString(16): 2a
int to BigInt: 999

Basic Arithmetic Operations

BigInt supports all standard arithmetic operations: addition, subtraction, multiplication, integer division, and remainder. The integer division operator ~/ truncates the result; regular / is not available because it would require a double intermediate which cannot represent all BigInt values exactly.

main.dart
void main() {
  var a = BigInt.parse('12345678901234567890');
  var b = BigInt.parse("987654321");
  
  print('Addition: ${a + b}');
  print('Subtraction: ${a - b}');
  print('Multiplication: ${a * b}');
  print('Division: ${a ~/ b}');
  print('Remainder: ${a % b}');
}

We perform basic arithmetic on two large BigInt values. The ~/ operator performs truncating integer division; the result drops any fractional part. The % operator returns the non-negative remainder.

$ dart main.dart
Addition: 12345678902222222211
Subtraction: 12345678900246913569
Multiplication: 12193263112482853211126352690
Division: 12499999887
Remainder: 339506163

Absolute Value and Sign

BigInt fully supports negative values. The abs method returns the absolute value, sign returns -1, 0, or 1, and the unary minus operator negates a value.

main.dart
void main() {
  var pos = BigInt.parse('123456789012345678901234567890');
  var neg = -pos;

  print('pos: $pos');
  print('neg: $neg');
  print('neg.abs(): ${neg.abs()}');
  print('pos.sign: ${pos.sign}');
  print('neg.sign: ${neg.sign}');
  print('BigInt.zero.sign: ${BigInt.zero.sign}');
  print('neg.isNegative: ${neg.isNegative}');
}

The unary minus operator creates the negative counterpart. abs always returns a non-negative value, while sign compactly encodes whether the number is negative, zero, or positive.

$ dart main.dart
pos: 123456789012345678901234567890
neg: -123456789012345678901234567890
neg.abs(): 123456789012345678901234567890
pos.sign: 1
neg.sign: -1
BigInt.zero.sign: 0
neg.isNegative: true

Comparison and Properties

BigInt values can be compared using standard operators. They also provide properties like isEven, isOdd, and sign.

main.dart
void main() {
  var x = BigInt.parse('12345678901234567890');
  var y = BigInt.parse('98765432109876543210');
  
  print('x < y: ${x < y}');
  print('x == y: ${x == y}');
  print('x.isEven: ${x.isEven}');
  print('y.isOdd: ${y.isOdd}');
  print('x.sign: ${x.sign}');
  print('y.bitLength: ${y.bitLength}');
}

We compare two BigInt values and inspect their properties. bitLength returns the minimum number of bits needed to represent the absolute value of the number (excluding the sign bit). For example, 8 requires 4 bits (1000₂).

$ dart main.dart
x < y: true
x == y: false
x.isEven: true
y.isOdd: false
x.sign: 1
y.bitLength: 67

Bitwise Operations

BigInt supports bitwise operations like AND, OR, XOR, and shifts. These are useful for low-level programming and cryptography.

main.dart
void main() {
  var a = BigInt.parse('0xFF00FF00FF00FF00FF');
  var b = BigInt.parse('0x00FF00FF00FF00FF00');
  
  print('AND: ${a & b}');
  print('OR: ${a | b}');
  print('XOR: ${a ^ b}');
  print('NOT a: ${~a}');
  print('Shift left: ${a << 4}');
  print('Shift right: ${a >> 8}');
}

We perform various bitwise operations on hexadecimal BigInt values. Note that ~a (bitwise NOT) on a non-negative BigInt produces a negative result, consistent with two's complement semantics.

$ dart main.dart
AND: 0
OR: 4722366482869645213695
XOR: 4722366482869645213695
NOT a: -4703991516010230251776
Shift left: 75263864256163684028400
Shift right: 18374966859414961920

GCD and Number Theory

The gcd method computes the greatest common divisor of two BigInt values — an essential building block in many number-theory algorithms. It is used, for example, to verify that two numbers are coprime before computing a modular inverse.

main.dart
void main() {
  var a = BigInt.parse('123456789012345678901234567890');
  var b = BigInt.parse('987654321098765432109876543210');

  print('gcd(a, b): ${a.gcd(b)}');

  // Check coprimality
  var p = BigInt.from(17);
  var q = BigInt.from(35);
  print('gcd(17, 35): ${p.gcd(q)}');
  print('17 and 35 are coprime: ${p.gcd(q) == BigInt.one}');

  // Reduce a fraction
  var num = BigInt.from(360);
  var den = BigInt.from(840);
  var g = num.gcd(den);
  print('360/840 reduced: ${num ~/ g}/${den ~/ g}');
}

gcd(a, b) always returns a non-negative value. Two integers are coprime when their GCD equals 1 — a requirement for modInverse to exist. The fraction-reduction example shows a practical real-world use.

$ dart main.dart
gcd(a, b): 90000000000000000000
gcd(17, 35): 1
17 and 35 are coprime: true
360/840 reduced: 3/7

Modular Arithmetic

BigInt provides methods for modular arithmetic, including pow and modPow which are essential for cryptographic algorithms.

main.dart
void main() {
  var base = BigInt.from(5);
  var exponent = BigInt.from(100);
  var modulus = BigInt.from(101);
  
  print('5^100: ${base.pow(100)}');
  print('5^100 mod 101: ${base.modPow(exponent, modulus)}');
  print('Modular inverse of 5 mod 101: ${base.modInverse(modulus)}');
}

We demonstrate exponentiation and modular operations. modPow uses fast modular exponentiation (square-and-multiply) to handle enormous exponents efficiently. modInverse finds the modular multiplicative inverse — i.e., the value x such that (base * x) % modulus == 1. A modular inverse only exists when the base and modulus are coprime.

$ dart main.dart
5^100: 7888609052210118054117285652827862296732064351090230047702789306640625
5^100 mod 101: 1
Modular inverse of 5 mod 101: 81

Factorial

Computing factorials of large numbers is a classic use case for BigInt. Even modest inputs like 100! produce values far beyond the range of a 64-bit integer.

main.dart
BigInt factorial(int n) {
  if (n <= 1) return BigInt.one;
  return BigInt.from(n) * factorial(n - 1);
}

void main() {
  for (var n in [10, 20, 50, 100]) {
    print('$n! = ${factorial(n)}');
  }
}

The function uses BigInt.one as the base case and multiplies BigInt.from(n) at each step. Standard Dart int would overflow around 20!, but BigInt handles any size effortlessly.

$ dart main.dart
10! = 3628800
20! = 2432902008176640000
50! = 30414093201713378043612608166979581188299763898377856340086048154919691977786015699310976576
100! = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Large Fibonacci Numbers

The Fibonacci sequence grows exponentially. Computing high-indexed Fibonacci numbers quickly exceeds 64-bit integer limits, making BigInt the right tool.

main.dart
BigInt fibonacci(int n) {
  var a = BigInt.zero;
  var b = BigInt.one;
  for (var i = 0; i < n; i++) {
    var temp = a + b;
    a = b;
    b = temp;
  }
  return a;
}

void main() {
  for (var n in [10, 50, 100, 500]) {
    var f = fibonacci(n);
    print('fib($n) = $f');
    print('  digits: ${f.toString().length}');
  }
}

The iterative approach avoids recursion overhead and stack limits. At n = 500 the result has over 100 decimal digits — impossible with native integers.

$ dart main.dart
fib(10) = 55
  digits: 2
fib(50) = 12586269025
  digits: 11
fib(100) = 354224848179261915075
  digits: 21
fib(500) = 139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125
  digits: 105

Best Practices

Source

Dart BigInt Documentation

This tutorial covered Dart's BigInt with practical examples demonstrating creation, radix parsing, type conversions, arithmetic, properties, bitwise operations, GCD, modular arithmetic, and real-world use cases such as factorial and large Fibonacci numbers.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.