Numbers in Dart
Dart apps often target multiple platforms. For example, a Flutter app might target iOS, Android, and the web. The code can be the same, as long as the app doesn’t rely on platformspecific libraries or use numbers in a way that’s platform dependent.
This page has details about the differences between native and web number implementations, and how to write code so that those differences don’t matter.
Dart number representation
In Dart, all numbers are part of the common Object
type hierarchy,
and there are two concrete, uservisible numeric types:
int
, representing integer values, and double
, representing fractional values.
Depending on the platform, those numeric types have different, hidden implementations. In particular, Dart has two very different types of targets it compiles to:
 Native: Most often, a 64bit mobile or desktop processor.
 Web: JavaScript as the primary execution engine.
The following table shows how Dart numbers are usually implemented:
Representation  Native int

Native double

Web int

Web double


64bit signed two's complement  ✅  
64bit floating point  ✅  ✅  ✅ 
For native targets, you can assume that
int
maps to a signed 64bit integer representation and
double
maps to a 64bit IEEE floatingpoint representation
that matches the underlying processor.
But on the web, where Dart compiles to and interoperates with JavaScript,
there is a single numeric representation:
a 64bit doubleprecision floatingpoint value.
For efficiency, Dart maps both int
and double
to this single representation.
The visible type hierarchy remains the same,
but the underlying hidden implementation types are
different and intertwined.
The following figure illustrates the platformspecific types (in blue)
for native and web targets.
As the figure shows,
the concrete type for int
on native implements only the int
interface.
However, the concrete type for int
on the web implements
both int
and double
.
An int
on the web is represented as
a doubleprecision floatingpoint value with no fractional part.
In practice, this works pretty well:
doubleprecision floating point provides 53 bits of integer precision.
However, int
values are always also double
values,
which can lead to some surprises.
Differences in behavior
Most integer and double arithmetic has essentially the same behavior. There are, however, important differences—particularly when your code has strict expectations about precision, string formatting, or underlying runtime types.
When arithmetic results differ, as described in this section, the behavior is platform specific and subject to change.
Precision
The following table demonstrates how some numerical expressions
differ due to precision.
Here, math
represents the dart:math
library,
and math.pow(2, 53)
is 2^{53}.
On the web, integers lose precision past 53 bits. In particular, 2^{53} and 2^{53}+1 map to the same value due to truncation. On native, these values can still be differentiated because native numbers have 64 bits—63 bits for the value and 1 for the sign.
The effect of overflow is visible when comparing 2^{63}1 to 2^{63}. On native, the latter overflows to 2^{63}, as expected for two’scomplement arithmetic. On the web, these values do not overflow because they are represented differently; they’re approximations due to the loss of precision.
Expression  Native  Web 

math.pow(2, 53)  1 
9007199254740991 
9007199254740991 
math.pow(2, 53) 
9007199254740992 
9007199254740992 
math.pow(2, 53) + 1 
9007199254740993 
9007199254740992 
math.pow(2, 62) 
4611686018427387904 
4611686018427388000 
math.pow(2, 63)  1 
9223372036854775807 
9223372036854776000 
math.pow(2, 63) 
9223372036854775808 
9223372036854776000 
math.pow(2, 64) 
0 
18446744073709552000 
Identity
On native platforms, double
and int
are distinct types:
no value can be both a double
and an int
at the same time.
On the web, that isn’t true.
Because of this difference,
identity can differ between platforms,
although equality (==
) doesn’t.
The following table shows some expressions that use equality and identity. The equality expressions are the same on native and web; the identity expressions are usually different.
Expression  Native  Web 

1.0 == 1 
true 
true 
identical(1.0, 1) 
false 
true 
0.0 == 0.0 
true 
true 
identical(0.0, 0.0) 
false 
true 
double.nan == double.nan 
false 
false 
identical(double.nan, double.nan) 
true 
false 
double.infinity == double.infinity 
true 
true 
identical(double.infinity, double.infinity) 
true 
true 
Types and type checking
On the web, the underlying int
type is like a subtype of double
:
it’s a doubleprecision value without a fractional part.
In fact, a type check on the web of the form x is int
returns true if x
is a number (double
) with
a zerovalued fractional part.
As a result, the following are true on the web:
 All Dart numbers (values of type
num
) aredouble
.  A Dart number can be both a
double
and anint
at the same time.
These facts affect is
checks and runtimeType
properties.
A side effect is that double.infinity
is interpreted as an int
.
Because this is a platformspecific behavior,
it might change in the future.
Expression  Native  Web 

1 is int 
true 
true 
1 is double 
false 
true 
1.0 is int 
false 
true 
1.0 is double 
true 
true 
(0.5 + 0.5) is int 
false 
true 
(0.5 + 0.5) is double 
true 
true 
3.14 is int 
false 
false 
3.14 is double 
true 
true 
double.infinity is int 
false 
true 
double.nan is int 
false 
false 
1.0.runtimeType 
double 
int 
1.runtimeType 
int 
int 
1.5.runtimeType 
double 
double 
Bitwise operations
For performance reasons on the web,
bitwise (&
, 
, ^
, ~
) and shift (<<
,>>
, >>>
) operators on int
use the native JavaScript equivalents.
In JavaScript, the operands are truncated to 32bit integers
that are treated as unsigned.
This treatment can lead to surprising results on larger numbers.
In particular, if operands are negative or don’t fit into 32 bits,
they’re likely to produce different results between native and web.
The following table shows how native and web platforms treat bitwise and shift operators when the operands are either negative or close to 32 bits:
Expression  Native  Web 

1 >> 0 
1 
4294967295 
1 ^ 2 
3 
4294967293 
math.pow(2, 32).toInt() 
4294967296 
4294967296 
math.pow(2, 32).toInt() >> 1 
2147483648 
0 
(math.pow(2, 32).toInt()1) >> 1 
2147483647 
2147483647 
String representation
On the web, Dart generally defers to JavaScript to convert a number to a string
(for example, for a print
).
The following table demonstrates how
converting the expressions in the first column can lead to different results.
Expression  Native toString()

Web toString()


1 
"1" 
"1" 
1.0 
"1.0" 
"1" 
(0.5 + 0.5) 
"1.0" 
"1" 
1.5 
"1.5" 
"1.5" 
0 
"0" 
"0.0" 
math.pow(2, 0) 
"1" 
"1" 
math.pow(2, 80) 
"0" 
"1.2089258196146292e+24" 
What should you do?
Usually, you don’t need to change your numeric code. Dart code has been running on both native and web platforms for years, and number implementation differences are rarely a problem. Common, typical code—such as iterating through a range of small integers and indexing a list—behaves the same.
If you have tests or assertions that compare string results, write them in a platformresilient manner. For example, suppose you’re testing the value of string expressions that have embedded numbers:
void main() {
var count = 10.0 * 2;
var message = "$count cows";
if (message != "20.0 cows") throw Exception("Unexpected: $message");
}
The preceding code succeeds on native platforms but throws on the web
because message
is "20 cows"
(no decimal) on the web.
As an alternative, you might write the condition as follows,
so it passes on both native and web platforms:
if (message != "${20.0} cows") throw ...
For bit manipulation, consider explicitly operating on 32bit chunks,
which are consistent on all platforms.
To force a signed interpretation of a 32bit chunk,
use int.toSigned(32)
.
For other cases where precision matters,
consider other numeric types.
The BigInt
type
provides arbitraryprecision integers on both native and web.
The fixnum
package
provides strict 64bit signed numbers, even on the web.
Use these types with care, though:
they often result in significantly bigger and slower code.