3.2 浮点数
Go具有两种大小的浮点数float32和float64。其算术特性遵从IEEE 754标准,所有新式CPU都支持该标准。
这两个类型的值可从极细微到超宏大。math包给出了浮点值的极限。常量math.MaxFloat32是float32的最大值,大约为3.4e38,而math.MaxFloat64则大约为1.8e308。相应地,最小的正浮点值大约为1.4e-45和4.9e-324。
十进制下,float32的有效数字大约是6位,float64的有效数字大约是15位。绝大多数情况下,应优先选用float64,因为除非格外小心,否则float32的运算会迅速累积误差。另外,float32能精确表示的正整数范围有限:
在源码中,浮点数可写成小数,如:
小数点前的数字可以省略(.707),后面的也可省去(1.)。非常小或非常大的数字最好使用科学记数法表示,此方法在数量级指数前写字母e或E:
浮点值能方便地通过Printf的谓词%g输出,该谓词会自动保持足够的精度,并选择最简洁的表示方式,但是对于数据表,%e(有指数)或%f(无指数)的形式可能更合适。这三个谓词都能掌控输出宽度和数值精度。
上面的代码按8个字符的宽度输出自然对数e的各个幂方,结果保留三位小数:
除了大量常见的数学函数之外,math包还有函数用于创建和判断IEEE 754标准定义的特殊值:正无穷大和负无穷大,它表示超出最大许可值的数及除以零的商;以及NaN(Not a Number),它表示数学上无意义的运算结果(如0/0或Sqrt(-1))。
math.IsNaN函数判断其参数是否是非数值,math.NaN函数则返回非数值(NaN)。在数字运算中,我们倾向于将NaN当作信号值(sentinel value),但直接判断具体的计算结果是否为NaN可能导致潜在错误,因为与NaN的比较总不成立(除了!=,它总是与==相反):
一个函数的返回值是浮点型且它有可能出错,那么最好单独报错,如下:
下一个程序以浮点绘图运算为例。它根据传入两个参数的函数z=f(x,y),绘出三维的网线状曲面,绘制过程中运用了可缩放矢量图形(Scalable Vector Graphics,SVG),绘制线条的一种标准XML格式。图3-1是函数sin(r)/r的图形输出样例,其中r为sqrt(x*x+y*y)。
图3-1 函数sin(r)/r的图形输出样例
注意,corner函数返回两个值,构成网格单元其中一角的坐标。
理解这段程序只需基本的几何知识,但略过也无妨,因为本例旨在说明浮点运算。这段程序本质上是三套不同坐标系的相互映射,见图3-2。首先是个包含100×100个单元的二维网格,每个网格单元用整数坐标(i, j)标记,从最远处靠后的角落(0, 0)开始。我们从后向前绘制,因而后方的多边形可能被前方的遮住。
图3-2 三套不同坐标系
第二个坐标系内,网格由三维浮点数(x, y, z)决定,其中x和y由i和j的线性函数决定,经过坐标转换,原点处于中央,并且坐标系按照xyrange进行缩放。高度值z由曲面函数f(x, y)决定。
第三个坐标系是二维成像绘图平面(image canvas),原点在左上角。这个平面中点的坐标记作(sx, sy)。我们用等角投影(isometric projection)将三维坐标点(x, y, z)映射到二维绘图平面上。若一个点的x值越大,y值越小,则其在绘图平面上看起来就越接近右方。而若一个点的x值或y值越大,且z值越小,则其在绘图平面上看起来就越接近下方。纵向(x)与横向(y)的缩放系数是由30°角的正弦值和余弦值推导而得。z方向的缩放系数为0.4,是个随意选定的参数值。
二维网格中的单元由main函数处理,它算出多边形ABCD在绘图平面上四个顶点的坐标,其中B对应(i, j),A、C、D则为其他三个顶点,然后再输出一条SVG指令将其绘出。
练习3.1:假如函数f返回一个float64型的无穷大值,就会导致SVG文件含有无效的<polygon>元素(尽管很多SVG绘图程序对此处理得当)。修改本程序以避免无效多边形。
练习3.2:用math包的其他函数试验可视化效果。你能否生成各种曲面,分别呈鸡蛋盒状、雪坡状或马鞍状?
练习3.3:按高度给每个多边形上色,使得峰顶呈红色(#ff0000),谷底呈蓝色(#0000ff)。
练习3.4:仿照1.7节的示例Lissajous的方法,构建一个Web服务器,计算并生成曲面,同时将SVG数据写入客户端。服务器必须如下设置Content-Type报头。
(在Lissajous示例中,这一步并不强制要求,因为该服务器使用标准的启发式规则,根据响应内容最前面的512字节来识别常见的格式(如PNG),并生成正确的HTTP报头。)允许客户端通过HTTP请求参数的形式指定各种值,如高度、宽度和颜色。