Neo中数字的表达和运算

96
沈寅
2018.03.17 17:11* 字数 3035

计算的问题

  1. 区块链一般有代币,而代币互相转移必然会设计到计算
  2. Neo中有GAS, 精确到小数点后8位,那么如何保障浮点数计算的正确
    带着上面两个问题,我研究了一下Neo源码,尝试学习理解一下一个带有激励机制的网络中,如何保证经济计算的正确。

Neo中数据的范围和精确度

  1. NEO 的有 1 亿管理代币,NEO 的最小单位为 1,不可再分割。
  2. GAS 是燃料代币,最大总量上限为 1 亿,用于实现对 NEO 网络使用时的资源控制。NEO 网络对代币转账和智能合约的运行和存储进行收费,从而实现对记账人的经济激励和防止资源滥用。GAS 的最小单位为 0.00000001。
  3. NEO和GAS的具体分发方法可以查看白皮书http://docs.neo.org/zh-cn/index.html
  4. 由于GAS是小数,必须保证计算时四舍五入的正确性。

综上所述,NEO系统中数值的范围是
NEO:0~100000000,精确度为1
GAS: 0.00000001~100000000.00000000

计算机中数值的表达

计算机系统一般来说是对现实世界的模拟,用来运算一个现实中遇到的问题,那么现实世界和计算机系统一般就有一个映射关系,我们现实世界中的各种数学概念中的数,在计算机中都要有一种表达方式。由于我的理论知识并不好,只能简单的描述一下我所认识到的计算机世界:

整型

整数在C#中是int,long,ulong等类型,我们看一下他的表示范围。

64位机器上C#类型 范围
int Signed: From −2,147,483,648 to 2,147,483,647, from −(231) to 231 − 1
long Signed: From −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807, from −(263) to 263 − 1

在C#中,int的范围是正负20亿左右(十进制),long的范围是
2的63次方=9223372036854775808,如果以米作单位,大约为光走一千年的距离;如果以秒作单位,大约为3000亿年,十进制是19位的一个数字。

由于Neo最大量是1亿,最小单位为1,在64位机器上做运算基本不会溢出,所以直接用long我觉得也是可以的。

浮点型:

浮点(英语:floating point,缩写为FP)是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值,称为浮点数(floating-point number)。利用浮点进行运算,称为浮点计算,这种运算通常伴随着因为无法精确表示而进行的近似或舍入。

在电脑使用的浮点数被电气电子工程师协会(IEEE)规范化为IEEE 754

并不是说小数叫做浮点数。准确的来说:“浮点数”是一种表示数字的标准,整数也可以用浮点数的格式来存储。

在C#的程序语言中,浮点数用float和double来表示。
EEE 754 规定,浮点数的表示方法为:


浮点数表示方法

浮点数表示举例
32位float

整型和浮点型都是64位的情况下,浮点型表示的范围更大,但是虽然范围大,可以表示的数字的数量,却是根据表示数的二进制位确定的。

decimal类型

decimal

从上表可以看出,decimal的有效位数很大,达到了28位,但是表示的数据范围却比float和double类型小。decimal类型并不是C#中的基础类型,所以使用的时候会对计算时的性能有影响。由于精度高,更适合用来处理金融系统的计算问题。但是其本身计算还是有误差的,精度再高都是实数轴上的离散点。

类型的比较

Type Approximate range Precision .NET Framework type
double ±5.0 × 10−324 to ±1.7 × 10308 15-16 digits System.Double
float -3.4 × 1038to +3.4 × 1038 7 digits System.Single
decimal (-7.9 x 1028 to 7.9 x 1028) / (100 to 1028) 28-29 significant digits System.Decimal
Type Range Size .NET Framework type
int -2,147,483,648 to 2,147,483,647 Signed 32-bit integer System.Int32
uint 0 to 4,294,967,295 Unsigned 32-bit integer System.UInt32
long -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 Signed 64-bit integer System.Int64
ulong 0 to 18,446,744,073,709,551,615 Unsigned 64-bit integer System.UInt64
sbyte -128 to 127 Signed 8-bit integer System.SByte
short -32,768 to 32,767 Signed 16-bit integer System.Int16

范围,精度,准确性

为了更进一步的讨论计算机中的数字,我们需要搞清楚范围,精度,准确性这三个概念。
数的范围是在数的当前表示格式下,最小的值到最大值中间的间隔。比如16位有符号的整数范围是-32768 to 32767,超过范围的就不能表示,需要注意的是,在范围内的有些数,在当前格式下也不能表示,比如整型就没法表示小数。所以每种类型都只能表达实数轴上离散的点。
精度和准确性通常让人混淆,这是两个完全不同的概念。精度是“表示数的格式”的一种属性,1.3333精确到小数点后四位,1.333300精确到小数点后6位,但他们是同一个值,但是1.333300的下一个数是1.333301,1.3333的下一个数是1.3334,可见高精度可以表示同一个范围内更多的数。
精度会影响到运算结果,所以在系统中需要特别注意这些问题:

Using one digit precision:
0.4 * 0.6 + 0.6 * 0.4   = 0.24 + 0.24   Calculate products
= 0.2 + 0.2 Round to 1 digit
= 0.4   Final result
Using two digit precision:
0.4 * 0.6 + 0.6 * 0.4   = 0.24 + 0.24   Calculate products
= 0.24 + 0.24   Keep the 2 digits
= 0.48  Calculate sum
= 0.5   Round to 1 digit

准确性需要在使用的上下文中讨论,表示运算的结果和真实的值之间的差异的大小,准确性和错误相关,高准确性意味着小的误差。准确性和精度是相关的,但是并不是直接相关,低精度在有些场景中,也是准确的,比如

Byte   n0 = 0x03;
Int16  n1 = 0x0003;
Int32  n2 = 0x00000003;
Single n3 = 3.000000f;
Double n4 = 3.000000000000000;

每种格式都准确表达了3,但是这些变量的精度是不同的,使用的位数也不一样,从8到64位。

舍入误差

假定你要在系统中使用pi,你会发现不管用任何类型的变量,你都无法准确表达它,你的计算必然包含着误差,你只能选择一个近似于pi的数去运算。你使用的不是pi,而是pi+e,假定e为舍入误差。
更不爽的是,你的给个运算都会带来新的误差,比如你需要计算(a+b),假定a,b和pi一样是无理数,那么实际上你计算的是(a + b) + (Ea + Eb + Esum)。
我们必须找到一种方法减少误差,使我们的结果可以反馈出实际的情况。可以看两个因为计算不准确导致的事故

  • 1990年2月25日,海湾战争期间,在沙特阿拉伯宰赫兰的爱国者导弹防御系统因浮点数舍入错误而失效,该系统的计算机精度仅有24位,存在0.0001%的计时误差,所以有效时间阙值是20个小时。当系统运行100个小时以后,已经积累了0.3422秒的误差。这个错误导致导弹系统不断地自我循环,而不能正确地瞄准目标。结果未能拦截一枚伊拉克飞毛腿导弹,飞毛腿导弹在军营中爆炸,造成28名美国陆军死亡。

  • 1996年6月4日,在亚利安五号运载火箭发射后37秒,偏离预定轨道而炸毁。原因是软件系统试图将64位浮点数转换为16位浮点数,造成溢出错误。

  • 温哥华证券交易所曾开发了一项股票指数。当其在1982年推出时,指数的值是1000.000。在后来的重新计算时多次运用舍入到小数点后三位的操作。22个月以后,指数的值是524.881,然而事实上应该是1009.811。

如何解决舍入误差

  1. https://en.wikipedia.org/wiki/Kahan_summation_algorithm
  2. 使用更高精度的类型,具体问题具体分析

选择在Neo中处理数据的类型

neon钱包
  1. Neo使用long来运算,精度没有问题,范围在普通的加减乘除情况下也很难溢出。我们可以看到1亿相乘也不会超过范围,但是继续相乘就会溢出了,但是不太可能有这种累加的效果,只需要代码中注意就好。
运算 大小
long.max 9223372036854775807
1亿*1亿 10000000000000000
  1. Gas的运算需要精确到小数点后8位
    我们假定支持最大的数字相乘而不溢出就是满足运算的范围要求,那么整数位需要1016,尾数位需要10-16
    Double类型在范围和精度上都满足要求
    decimal在范围和精度上满足要求

现在虽然已经给Neo选择好类型,但是我们进入代码,会发现Neo使用了一个叫Fixed8的类型表示数据,所以我们进一步讨论。

定点数

简单的来说,定点数就是小数点在特定位置的数,比如Neo中的fixed8就是始终保持小数点后8位。
定义一个定点数,需要包含两部分:

  1. 有多少位
  2. 小数点在哪里

定点数实际上使用整型表达就可以了,我们只需要另外保存一下精度,比如Fixed8就是把long型除以100000000。下图显示了定点数的格式


定点数如何保存

定点数的优缺点

  1. 由于使用整型保存,所有的计算方法其实和整型类似
  2. 相同二进制位数下,与整型相比,表示的范围缩小了,因为精度提高了,也可以把整型看成精度为1的定点数。
  3. 与浮点数相比,定点数运算速度更快。

fixed8中的一个bug

在看源码的时候,发现fixed8的乘法是使用位移方法自己实现的,结果发现其准确性并没有两个decimal相乘高,虽然如此小的误差不一定会给系统带来什么影响,但是还是一个有趣的发现。
https://github.com/neo-project/neo/issues/194

极大的数

如果数字的范围和精度都极大,那么就需要特殊处理,Neo中使用了BigInteger封装了一个BigDecimal的结构体。BigInteger二的地方是不支持小数,所以Neo中的小数必须乘以一个精度转成BigDecimal,BigDecimal只是保存了一下精度。

UInt256, UInt160

在Neo中发现还有两个基于byte的数据类型UInt256,UInt160。这两个主要用来保存hash算法的的值。
UInt256有32的字节大小,256个bit,用来保存"22c199231f06e2091f89e0128e7d875fdfb0fb5fc7c4f916af0c50d04ab70e74"这样的字符串。这个字符串是64个字符组成的,而64位机器上,char类型是2个字节,为什么用32位就可以表示呢?我们看看下面代码。

 public static byte[] HexToBytes(this string value)
        {
            if (value == null || value.Length == 0)
                return new byte[0];
            if (value.Length % 2 == 1)
                throw new FormatException();
            byte[] result = new byte[value.Length / 2];
            for (int i = 0; i < result.Length; i++)
                result[i] = byte.Parse(value.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier);
            return result;
        }

这段代码表示把以字符串表示的的16进制的数转成byte数组。所以只要hash的目标集合在0~f之间就可以。
我们可以看到"22c199231f06e2091f89e0128e7d875fdfb0fb5fc7c4f916af0c50d04ab70e74"就在这个范围,如果你改一下,比如加个z进去,就会抛出异常了。

总结

上面只是简单的介绍,基本上是很容易理解的,但是实际上数的表示和运算是一个非常基础的问题,越简单的问题往往包含着很复杂的原理,下面的参考文档有很多有意思的细节,建议阅读。

参考文档

neo白皮书
Floating Point in .NET part 1: Concepts and Formats
Introduction to Fixed Point Math for Embedded Systems - Part 1 of 3
Fixed Point Arithmetic 1: Intro to Fixed Point
Fixed Point and Floating Point binary numbers
fixed point representation in computer organization| COA
Floating Point/Fixed-Point Numbers

区块链
Web note ad 1