Dart 中的数字
Dart 应用通常面向多个平台。例如,Flutter 应用可能面向 iOS、Android 和 Web。只要应用不依赖于特定于平台的库或以依赖于平台的方式使用数字,代码就可以相同。
此页面详细介绍了本机和 Web 数字实现之间的差异,以及如何编写代码以使这些差异无关紧要。
Dart 数字表示
#在 Dart 中,所有数字都是通用 Object 类型层次结构的一部分,并且有两个具体的、用户可见的数值类型:int,表示整数值,double,表示分数值。
根据平台的不同,这些数值类型具有不同的隐藏实现。特别是,Dart 有两种截然不同的目标编译类型
- **原生:**最常见的是 64 位移动或桌面处理器。
- **Web:**JavaScript 作为主要执行引擎。
下表显示了 Dart 数字通常如何实现
| 表示 | 原生 int | 原生 double | Web int | Web double |
|---|---|---|---|---|
| 64 位有符号二进制补码 | ✅ | |||
| 64 位浮点数 | ✅ | ✅ | ✅ |
对于原生目标,您可以假设 int 映射到有符号 64 位整数表示,double 映射到与底层处理器匹配的 64 位 IEEE 浮点表示。
但在 Web 上,Dart 编译到并与 JavaScript 互操作的地方,只有一个数值表示:64 位双精度浮点值。为了提高效率,Dart 将 int 和 double 都映射到此单个表示。可见的类型层次结构保持不变,但底层的隐藏实现类型不同且相互交织。
下图说明了原生和 Web 目标的特定于平台的类型(蓝色)。如图所示,原生上 int 的具体类型仅实现 int 接口。但是,Web 上 int 的具体类型同时实现了 int 和 double。
Web 上的 int 表示为没有小数部分的双精度浮点值。在实践中,这运行良好:双精度浮点提供了 53 位的整数精度。但是,int 值始终也是 double 值,这可能会导致一些意外情况。
行为差异
#大多数整数和双精度算术运算基本上具有相同的行为。但是,也存在重要的差异——尤其是在您的代码对精度、字符串格式或底层运行时类型有严格期望时。
当算术结果不同时,如本节所述,行为是**特定于平台的**且**可能发生变化**。
精度
#下表演示了由于精度而导致的一些数值表达式如何不同。这里,math 表示 dart:math 库,math.pow(2, 53) 是 253。
在 Web 上,整数在超过 53 位后会丢失精度。特别是,由于截断,253 和 253+1 映射到相同的值。在原生上,这些值仍然可以区分,因为原生数字有 64 位——63 位用于值,1 位用于符号。
溢出的影响在比较 263-1 与 263 时可见。在原生上,后者溢出到 -263,这对于二进制补码算术来说是预期的。在 Web 上,这些值不会溢出,因为它们的表示方式不同;由于精度损失,它们是近似值。
| 表达式 | 原生 | 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 |
标识
#在原生平台上,double 和 int 是不同的类型:没有值可以同时是 double 和 int。在 Web 上,情况并非如此。由于这种差异,标识可能因平台而异,尽管相等性 (==) 不会。
下表显示了一些使用相等性和标识性的表达式。相等性表达式在原生和 Web 上相同;标识性表达式通常不同。
| 表达式 | 原生 | 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 |
类型和类型检查
#在 Web 上,底层的 int 类型类似于 double 的子类型:它是没有小数部分的双精度值。实际上,Web 上表单 x is int 的类型检查会在 x 是一个具有零值小数部分的数字 (double) 时返回 true。
因此,以下在 Web 上为真
- 所有 Dart 数字(类型为
num的值)都是double。 - Dart 数字可以同时是
double和int。
这些事实会影响 is 检查和 runtimeType 属性。一个副作用是 double.infinity 被解释为 int。由于这是特定于平台的行为,因此将来可能会更改。
| 表达式 | 原生 | 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 |
按位运算
#出于 Web 性能方面的考虑,int 上的按位运算符(&、|、^、~)和移位运算符(<<、>>、>>>)使用原生 JavaScript 等价物。在 JavaScript 中,操作数会被截断为 32 位整数,并被视为无符号数。这种处理方式可能会导致对较大数字产生意外的结果。特别是,如果操作数为负数或不适合 32 位,则它们很可能在原生和 Web 之间产生不同的结果。
下表显示了原生平台和 Web 平台在操作数为负数或接近 32 位时如何处理按位运算符和移位运算符。
| 表达式 | 原生 | 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 |
字符串表示
#在 Web 上,Dart 通常依赖 JavaScript 将数字转换为字符串(例如,用于 print)。下表演示了如何转换第一列中的表达式会导致不同的结果。
| 表达式 | 原生 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" |
您应该怎么做?
#通常,您不需要更改数字代码。Dart 代码多年来一直在原生和 Web 平台上运行,数字实现差异很少成为问题。常见的典型代码(例如遍历一系列小整数并索引列表)的行为相同。
如果您有比较字符串结果的测试或断言,请以平台弹性的方式编写它们。例如,假设您正在测试包含嵌入数字的字符串表达式的值。
void main() {
var count = 10.0 * 2;
var message = "$count cows";
if (message != "20.0 cows") throw Exception("Unexpected: $message");
}上述代码在原生平台上成功,但在 Web 上抛出异常,因为 message 在 Web 上为 "20 cows"(没有小数)。作为替代方案,您可以按如下方式编写条件,以便它在原生和 Web 平台上都通过。
if (message != "${20.0} cows") throw ...对于位操作,请考虑显式地操作 32 位块,这些块在所有平台上都一致。要强制对 32 位块进行有符号解释,请使用 int.toSigned(32)。
对于其他需要精度的用例,请考虑其他数值类型。BigInt 类型在原生和 Web 上都提供了任意精度的整数。fixnum 包提供了严格的 64 位有符号数,即使在 Web 上也是如此。但是,请谨慎使用这些类型:它们通常会导致代码变得更大、更慢。
除非另有说明,否则本网站上的文档反映了 Dart 3.5.3。页面最后更新于 2024-02-07。 查看源代码 或 报告问题.