蒋海云个人博客,日拱一卒。 2017-08-28T15:19:56+08:00 jiang.haiyun#gmail.com Angular docs-模板与数据绑定-显示数据 2017-08-28T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/angular-docs-template_data_binding_displaying_data 模板与数据绑定-显示数据

使用 `` 来显示组件属性值

项目为 displaying-data。例子为 :

//src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1></h1>
    <h2>My favorite hero is: </h2>
    `
})
export class AppComponent {
  title = 'Tour of Heroes';
  myHero = 'Windstorm';
}

Angular 自动从组件中提取 titlemyHero 这两个属性的值,并插入到 DOM 中。同时当这两个属性值有变动时,也会自动更新 DOM 中的对应信息(更加确切地说,更新发生在一些与该视图相关的异步事件之后,例如 keystroke, timer competion, HTTP 请求应答等)。

注意我们没有调用 new 来创建 AppComponent 实例。Angular 会自动创建。当在 main.ts 中启动AppComponent 后,Angular 会在 index.html 中看到 <my-app> 标签,此时,它将实例化一个 AppComponent 实例,并将它呈现在 <my-app> 标签中。

使用构造器还是用变量赋值法

上例是直接使用变量赋值法的(更加简洁)。不过也可以先声明属性,再在构造器中初始化,它们是造价的。如下:

//src/app/app-ctor.component.ts (class)
export class AppCtorComponent {
  title: string;
  myHero: string;

  constructor() {
    this.title = 'Tour of Heroes';
    this.myHero = 'Windstorm';
  }
}

使用构造器参数隐式创建属性的 TypeScript 简写法

//src/app/hero.ts (excerpt)
export class Hero {
  constructor(
    public id: number,
    public name: string) { }
}

这样就通过构造器参数隐式创建了两个属性: idname

public id: number, 为例,这个简洁写法含义有:

  • 声明了一个构造器参数及其类型
  • 声明了一个同名的公开属性
  • 当创建该类的实例时,用对应参数值一并初始化该属性

参考

]]>
Angular docs-体系结构 2017-08-28T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/angular-docs-fundamentals_architecture 体系结构

概述

写 Angular 应用的方式:用 Angular 化的标签组织 HTML 模板 template,编写组件 component 类来管理这些模板,在服务 service 中添加应用逻辑,最后在模块 module 中组合组件和服务。

最后通过启动根模块 root module 来加载应用。

Angular 体系结构

模块 Module

Angular 应用是模块化的,Angular 有自己的模块化系统,叫 NgModules

每个应用都至少有一个 NgModule 类,即 root module,通常命名为 AppModule

复杂的系统会有多个 module,即每个特性一个 module。

一个 NgModule,实际上就是一个带有 @NgModule 装饰器的类。

NgModule 装饰函数接收一个 metadata 对象作为参数,该对象中的属性值用来描述该模块。其中最重要的属性有:

  • declarations: 属性该模块的视图类。共有 3 种视图类: 组件(component),指令 (directive) 和 管道(pipe)。
  • exports: declarations 的一个子集,这些能在组件模板和其它模块中可访问。
  • imports: 其它模块,它们的导出类要在本模板中声明的组件模板访问。
  • providers: 服务(service)的生成器,这里面的服务将变成全局的,可在所有组件中使用。
  • bootstrap: 应用的主视图,称为 root component。只有 root module 才需要设置该属性。

根模块的简单例子:

//src/app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ Logger ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

通过启动根模块来加载应用,在开发时一般在 main.ts 中启动 AppModule:

//src/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

NgModule vs JavaScript 模块

NgModule 就是带有 @NgModule 装饰器的类,它是 Angular 的一个基本特性。

而 JavaScript 自己的模块系统用来管理 JavaScript 的对象集。JavaScript 中一个文件就是一个模块,里面定义的对象都属性该模块。需要公开的对象使用 export 关键字声明,而其它模块通过 import 语句导入公开的对象后才能使用:

import { NgModule }     from '@angular/core';
import { AppComponent } from './app.component';

export class AppModule { }

Angular 库

Angular 带有一些 JavaScript 模块,可认为是库模块,它们的名字中都带有 @angular 前缀。通过 npm 安装,并使用 import 加载。

例如,从库中 import Component 装饰器:

import { Component } from '@angular/core';

也可以从库中 import NgModule:

import { BrowserModule } from '@angular/platform-browser';

在上面的 root module 例子中,应用模块需要 BrowserModule 中的东西,因此,需要将它添加到 @NgModule metadata 的 imports 中:

imports:      [ BrowserModule ],

组件 Component

一个组件控制一个视图。

支持视图的组件逻辑定义在一个类中。类通过属性和方法与视图交互。

当用户使用应用过程中,Angular 会自动创建、更新和销毁各组件。而通过 lifecycle hooks,如 ngOnInit(),可以定制组件生命期间各时刻的行为。

模板 Template

组件视图通过模板定义。一个模板是用 Angular 化的标签组成的 HTML,如:

//src/app/hero-list.component.html
<h2>Hero List</h2>

<p><i>Pick a hero from the list</i></p>
<ul>
  <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
    
  </li>
</ul>

<hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>

组件树

Metadata

Metadata 告诉 Angular 如何处理一个类。

在 TypeScript 中,通过使用装饰器来关联 metadata,如:

//src/app/hero-list.component.ts (metadata)
@Component({
  selector:    'hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}

template-metadata-component

上例中,@Component 中的 metadata 告诉 Angular 到哪里为该组件获取主要的构建块。

模板、metadata 和组件共同描述一个视图。

数据绑定

Angular 支持数据绑定,即一种将组件中的部件与模板中的部件关联起来的一种机制。在模板 HTML 中添加绑定标签,告诉 Angular 如何连接两端。

databinding

从图中可见,共有 4 种 绑定形式语法。每种形式都有一个方向:到 DOM,从 DOM,或双向。

下例的模板中用了 3 种形式:

//src/app/hero-list.component.html (binding)
<li></li>
<hero-detail [hero]="selectedHero"></hero-detail>
<li (click)="selectHero(hero)"></li>

第 4 种形式是很重要的双向数据绑定,它使用 ngModel 指令将属性和事件绑定组合在一个语句中,例如:

//src/app/hero-detail.component.html (ngModel)
<input [(ngModel)]="hero.name">

上例中,作为属性绑定,属性值从组件流向 input。而作为事件绑定,用户的修改也会流回到组件。

从根组件树到所有的子组件,Angular 在每次 JavaScript 事件周期中一次性处理所有的数据绑定。

数据绑定在模板与模板的交互,以及父子组件间的交互中起来了重要的作用。

component-databinding

component-databinding

指令 Directive

Angular 模板是动态的。当呈现时,它根据 Directive 中的指令变换 DOM。

一个 Directive 就是一个带有 @Directive 装饰器的类。@Component 装饰器实际上就是带有模板功能特性的 @Directive 装饰器的扩展装饰器,因此,一个组件实际上就是一个带有模板的 Directive。

共有 2 种指令,结构指令(structural directive) 和属性指令(attribute directive)。

它们通常像标签的属性一样出现,有时是以名字形式出现,但更多时候是作为赋值或绑定语句的目标值出现。

结构指令通过在 DOM 中添加、删除和替换元素来修改布局。

下面的模板中使用了 2 个内置的结构指令:

//src/app/hero-list.component.html (structural)
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>

属性指令修改一个现有元素的外观或行为。在模板中它们使用名字的形式,因而像普通的 HTML 属性。。

ngModel 就是一个属性指令,它实现双向数据绑定。ngModel 修改了元素(一般是 <input>) 的行为:设置其显示值属性,并响应修改事件。

//src/app/hero-detail.component.html (ngModel)
<input [(ngModel)]="hero.name">

Angular 有很多指令,如 ngSwitch, ngStyle, ngClass, 不过也可以自己编写指令。

服务 Service

服务 (Service) 可以包含值、函数、或应用所有的功能。Angular 对服务没有特别的定义,没有 service base class, 无需注册等。一个完成特定功能的类就可以称为是一个服务。

组件的工作通常只关注用户体验。它在视图和应用逻辑间作协调。一个好组件只为数据绑定呈现属性和方法,而其它的所有事情都委派服务来完成。

依赖注入 Dependency injection

依赖注入是供应所需(所依赖)的类的新实例的一种新方式。大多数依赖都是服务。Angular 使用依赖注入为新创建的组件提供所需的服务。

Angular 通过组件构造器的参数,可了解组件所需的服务,例如下面的 HeroListComponent 需要 HeroService

//src/app/hero-list.component.ts (constructor)
constructor(private service: HeroService) { }

当创建一个组件中,Angular 会首先向一个注射器(injector)请求该组件所需的服务。

一个注射器组件一个容器,里面包含了它之前已创建的所有服务实例。如果请求的服务实现还没有有容器中,注射器会创建一个并添加后容器后,之后才返回。当所请求的服务都返回后,Angular 将这些服务实例作为参数来调用组件的构造器。这就是依赖注入。

HeroService 的注入过程如下:

injector-injects

当注射器中没有 HeroService 时,它需要创建一个,因此,我们必须为注射器事物注册该 HeroService 的一个 provider。一个 provider 就是一个能创建或返回一个服务的东西,通常就是服务类本身。

可以在模块或组件中注册 provider。

通常,在根组件的 providers 中注册后,该服务的相同实例可以在所有地方使用(单例模式):

//src/app/app.module.ts (module providers)
providers: [
  BackendService,
  HeroService,
  Logger
],

而在 @Component 的 metadata 中注册的是组件级的,因此当每次创建该组件实例时,都会得到一个新的服务实例:

//src/app/hero-list.component.ts (component providers)
@Component({
  selector:    'hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})

依赖注入的要点:

  • 依赖注入在 Angular 框架中有大量使用
  • 注射器 injector 是主要机制:
    • injector 维护一个它的服务实例的一个容器
    • injector 能根据 provider 创建一个新的服务实例
  • 一个 provider 就是创建一个服务的一种方法
  • 用注射器注册 providers

参考

]]>
Javascript Promise 2017-08-23T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/javascript-promise 概述

Promise 对象用来表示一个异步操作的最终结果情况,当操作成功时是一个值,操作失败时是一个错误对象。使用 Promise 后,异步函数也像同步函数一样返回值:不是立即返回最终值,而是返回一个 Promise,该 Promise 在未来的某个时间点会提供一个值。

Promise 可通过其构造器创建。但是一般是使用从函数调用中返回的 Promise 对象。

一个 Promise 对象本质上就是可以将回调函数与它关联的返回对象。

一个 Promise 有 3 种状态:

  • pending: 初始状态
  • fulfilled: 操作已成功完成
  • rejected: 操作失败

Promise 的初始状态是 pending,操作成功完成后,变成 fulfilled 并产生一个值,操作失败后变成 rejected 并附带一个错误原由。无论操作成功还是失败后,通过 Promise .then() 添加的关联回调函数都会被调用。

传统上,一般通过传入两个回调函数来使用异步操作:

function successCallback(result) {
  console.log("It succeeded with " + result);
}

function failureCallback(error) {
  console.log("It failed with " + error);
}

doSomething(successCallback, failureCallback);

而现在的做法是:函数立即返回一个 Promise 对象,然后将回调函数关联进来:

let promise = doSomething(); 
promise.then(successCallback, failureCallback);

// or
doSomething().then(successCallback, failureCallback);

相比旧方法,使用 Promise 有以下优点:

  • 回调函数不会在当前 Javascript 事件循环执行完毕前执行,即回调函数总是先放在一个队列中,稍后执行
  • 通过 .then() 添加的回调函数,即使是在异步操作完成后添加的,也会被调用
  • 可多次调用 .then(),添加多个回调函数,它们的执行顺序与添加顺序无关。

Promise 构造器

语法

new Promise( /* executor */ function(resolve, reject){ ... });

参数

executor 参数是一个函数,该函数有两个参数 resolve 函数对象和 reject函数对象。executor 函数在创建 Promise 实例时会立即执行(在 Promise 构造器返回创建的对象前执行)。executor 通常初始化一些异步任务,然后,当完成后,如果成功,则调用 resolve,如果失败,则调用 reject

属性

Promise.length 表示其构造器的参数个数,值总是为 1。

方法

Promise.all(iterable): 若参数列表中所有 Promise 都成功操作完成,则返回一个 fulfilled 的 Promise,关联的返回值是一个数组,元素是参数中各 Promise 的关联返回值,值顺序与参数顺序相同。 若其中有一个 Promise 失败,则返回一个 reject 的 Promise,关联的返回值是第一个 rejected Promise 的关联返回值。

Promise.race(iterable): 一旦参数列表中某个 Promise 操作完成时,则立即返回一个 Promise,关联值是参数中操作完成的 Promise 的返回关联值。

Promise.reject(reason): 立即返回一个 rejected 状态的 Promise 对象。

Promise.resolve(value): 一般返回一个 fulfilled 状态的 Promise 对象。但如果 value 是 thenable 的(即有 then 函数),则返回的 Promise 将 “follow” 该 thenable,并采用其最终状态。

Promise.prototype.catch(onRejected): 添加一个 onRejected 关联回调函数。

Promise.prototype.then(onFullfilled, onRejected): 添加 onFullfilled 和 onRejected 两个关联回调函数。

链式

当需要按顺序执行多个异步操作时,可创建一个 Promise 链:

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

//or
//let promise2 = doSomething().then(successCallback, failureCallback);

promise2 不仅只表示 doSomething() 的完成情况,还表示传入的 successCallback 或 failureCallback 的完成情况(它们也可以是一个返回 Promise 的异步函数)。而每个 Promise 对象基本上表示链中这个异步操作步骤的操作结果。

以前完成多个异步操作时,代码会变成倒金字塔形状:

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

使用 Promise 后变为:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

这里每一步的回调函数都返回一个 Promise。

当某步操作失败后(即执行 catch后),也可以继续链下去:

new Promise((resolve, reject) => {
    console.log('Initial');

    resolve();
})
.then(() => {
    throw new Error('Something failed');
        
    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('Do this whatever happened before');
});

将输出:

Initial
Do that
Do this whatever happened before

错误处理

一般地,当 Promise 链中出现异常时将停止,并查询链中接下来的 catch 处理函数:

doSomething()
.then(result => doSomethingElse(value))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);

这种处理方式是根据同步操作代码的工作模式建模的:

try {
  let result = syncDoSomething();
  let newResult = syncDoSomethingElse(result);
  let finalResult = syncDoThirdThing(newResult);
  console.log(`Got the final result: ${finalResult}`);
} catch(error) {
  failureCallback(error);
}

这种与同步操作代码的对称性在 ECMAScript 2017 版中演化成了 async/await 语法糖,如:

async function foo() {
  try {
    let result = await doSomething();
    let newResult = await doSomethingElse(result);
    let finalResult = await doThirdThing(newResult);
    console.log(`Got the final result: ${finalResult}`);
  } catch(error) {
    failureCallback(error);
  }
}

将旧的回调函数 API 封闭成一个 Promise

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);

组合

Promise.all()Promise.race() 是并行运行异步操作的两个组合工具。

Promise.resolve()Promise.reject() 是手工创建 fulfilled 或 rejected Promise 的方法。

顺序执行异步操作的组合,可以通过 Array.reduce 实现。

Array.reduce(callback[, initialValue]) 将 callback 应用于数组中的每个元素,最终生成一个值。而 callback 是一个接收 2 个参数的函数。

按顺序组合:

[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());

即将一组异步函数 reduce 成如下等价的 Promise 链:

Promise.resolve().then(func1).then(func2);

这可以发展一个通用的组合函数(在函数式编程中很常见):

let applyAsync = (acc,val) => acc.then(val);
let composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

然后这样使用:

let transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
transformData(data);

在 ECMAScript 2017 中,顺序组合可以简单地用 async/await 完成:

for (let f of [func1, func2]) {
  await f();
}

延时

通过 then() 添加的回调函数不会同步执行:

Promise.resolve().then(() => console.log(2));
console.log(1); // 1, 2

即它们都会先被放到一个任务队列中,不会立即执行:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait().then(() => console.log(4));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));
console.log(1); // 1, 2, 3, 4

参考

]]>
Raspberry 上安装 OSMC 实现大屏消息分发系统 2017-08-21T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/raspberry-pi3-deploy-osmc 安装系统

下载

到官方网站下载 OSMC 映像文件,当前版本是 OSMC_TGT_rbp2_20170803.img。

将系统写入 SD 卡

树莓派 3 支持的是 microSD 卡,我用的是 SanDisk microSDXC I。

在 Ubuntu 系统上先查看当前的硬盘信息:

$ sudo fdisk -l

Device     Boot     Start        End    Sectors   Size Id Type
/dev/sda1  *         2048  107319295  107317248  51.2G  7 HPFS/NTFS/exFAT
/dev/sda2       107319296  209717247  102397952  48.8G  7 HPFS/NTFS/exFAT
/dev/sda3       209719294  817717247  607997954 289.9G  5 Extended
/dev/sda4       817717248 1953519615 1135802368 541.6G  7 HPFS/NTFS/exFAT
/dev/sda5       209719296  809717759  599998464 286.1G 83 Linux
/dev/sda6       809719808  817717247    7997440   3.8G 82 Linux swap / Solaris

Partition 3 does not start on physical sector boundary.
Partition table entries are not in disk order.

由于我只有一个硬盘,该命令列出了我的 sda 硬盘的信息。

接着将 SD 卡插入系统,再次运行 fdisk 命令:

$ sudo fdisk -l

Device     Boot     Start        End    Sectors   Size Id Type
/dev/sda1  *         2048  107319295  107317248  51.2G  7 HPFS/NTFS/exFAT
/dev/sda2       107319296  209717247  102397952  48.8G  7 HPFS/NTFS/exFAT
/dev/sda3       209719294  817717247  607997954 289.9G  5 Extended
/dev/sda4       817717248 1953519615 1135802368 541.6G  7 HPFS/NTFS/exFAT
/dev/sda5       209719296  809717759  599998464 286.1G 83 Linux
/dev/sda6       809719808  817717247    7997440   3.8G 82 Linux swap / Solaris

Partition 3 does not start on physical sector boundary.
Partition table entries are not in disk order.



Disk /dev/mmcblk0: 14.9 GiB, 15931539456 bytes, 31116288 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000

Device         Boot Start      End  Sectors  Size Id Type
/dev/mmcblk0p1       8192 31116287 31108096 14.9G  c W95 FAT32 (LBA)

可以看到多了一个设备 /dev/mmcblk0 ,这个就是 SD 卡。

将下载下来的系统映射文件解压, 解压过程需要点时间,解压后得到 OSMC_TGT_rbp2_20170803.img。

如果 SD 卡显示挂载了, 先将 SD 卡卸载。

使用 dd 命令将映像文件写入 SD 卡中:

$ sudo dd bs=8M if=OSMC_TGT_rbp2_20170803.img  of=/dev/mmcblk0

执行这个命令可能需要好几分钟。

至此,系统已经写入 SD 卡中。

上电及初始化设置

按照提示一步步设置 OSMC 的时区、语言、网络等,并开启 SSH服务。

假设设置的无线网卡(v3 内置有无线网卡和蓝牙)的地址是 192.168.31.199。

ssh 登录后进一步进行设置(默认的用户名和密码都是 osmc):

$ ssh osmc@192.168.31.199

登录后,更新系统里的软件:

$ sudo apt-get update
$ sudo apt-get upgrade

设置系统的键盘为 USA-English。

设置每天自动关机

在 /root/.bashrc 中添加 export EDITOR=vi,设置默认编辑器。

$ sudo apt-get install cron
$ sudo crontab -e

在文件的最后一行添加下列内容:

25 16 * * * /sbin/shutdown -h now

表示在每天的 16:25 执行关机命令 shutdown -h now

添加插件源

File manager -> Add source: superrepo,添加的源地址为 http://srp.nu

实现启动后自动开启幻灯片播放功能

安装 Picture Slidershow Screensave 插件

Settings -> Add-on browser -> Install from repository -> Look and feel -> Screensaver -> Picture Slideshow Screensaver -> Install

设置 Picture Slideshow Screensaver 插件

Settings -> Add-on browser -> My add-ons -> Look and feel -> Screensaver -> Picture Slideshow Screensaver -> Configure:

Basic:

Source of slideshow images: Image Folder

Home folder -> Pictures

Amount of seconds to display each image: 30s

Effect: Crossfade

Dim: 100%

Additional: Display background picture: 不选中

Auto-update: 不选中

Settings -> Interface -> Screensaver -> Wait time: 1m

参考: http://kodi.wiki/view/Add-on:Multi_Slideshow_Screensaver 和 https://discourse.osmc.tv/t/auto-start-slideshow-on-boot/1476

安装 Chorus2 进行 Web 管理

管理界面项目地址为 https://github.com/xbmc/chorus2

在 Settings -> Services -> Control 中进行设置。

FTP 服务

安装 FTP 服务端

My Program -> App Store -> Ftp Server: Install

客户端

使用 Filezilla ftp client

VNC 服务

安装 VNC 服务端

第一步:安装编译所需的依赖包。

$ sudo apt-get install build-essential rbp-userland-dev-osmc libvncserver-dev libconfig++-dev unzip
$ cd /home/osmc
$ sudo wget https://github.com/patrikolausson/dispmanx_vnc/archive/master.zip
$ unzip master.zip -d  /home/osmc/
$ rm master.zip
$ cd dispmanx_vnc-master
$ make

第二步:将 vnc server 添加成为服务。

$ sudo cp dispmanx_vncserver /usr/bin
$ sudo chmod +x /usr/bin/dispmanx_vncserver
$ sudo cp dispmanx_vncserver.conf.sample /etc/dispmanx_vncserver.conf
$ sudo vi /etc/dispmanx_vncserver.conf

配置文件修改成为:

relative = false;
port = 0;
screen = 0;
unsafe = false;
fullscreen = false;
multi-threaded = false;
password = "mypassword";
frame-rate = 23;
downscale = false;
localhost = false;
vnc-params = "";

第三步:创建 service 文件,实现随系统自动启动。

$ sudo vi /etc/systemd/system/dispmanx_vncserver.service

内容修改为:

[Unit]
Description=VNC Server
After=network-online.target mediacenter.service
Requires=mediacenter.service

[Service]
Restart=on-failure
RestartSec=30
Nice=15
User=root
Group=root
Type=simple
ExecStartPre=/sbin/modprobe evdev
ExecStart=/usr/bin/dispmanx_vncserver
KillMode=process

[Install]
WantedBy=multi-user.target

最后,重启系统或者用以下命令开启 svn 服务:

$ sudo systemctl start dispmanx_vncserver.service
$ sudo systemctl enable dispmanx_vncserver.service
$ sudo systemctl daemon-reload

参考 Install a vnc server on the Raspberry pi

备份 SD 卡

cd workspace/
$ sudo dd bs=8M if=/dev/mmcblk0 of=OSMC_screen_delivery20170820.img
]]>
Python 2 标准库示例:5.3 random-伪随机数生成器 2017-08-16T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/Python2Lib-math-random 目的: 实现了几种类型的伪随机数生成器。

Python 版本: 1.4+。

random 模块基于多种不同的分布实现了伪随机数生成器。几乎所有的模块函数都基于 random() 函数,该函数使用均匀分布创建一个在区间 [0.0, 1.0) 之间的一个随机浮点数。

模块函数实际上都是绑定于一个隐含的 random.Random 的类实例。我们也可以创建独立的 random.Random 实现,以获取独立的生成器状态信息。例如在多线程编程中,可为每个线程创建一个单独的 random.Random 实例。

生成随机数

random() 函数从一个生成器生成的序列中返回下一个随机浮点数,返回值在 [0.0, 1.0) 之间。

import random

for i in xrange(5):
    print '%04.3f' % random.random(),
print
0.999 0.106 0.006 0.846 0.101

使用 uniform(min, max) 函数可以生成在 [min, max) 之间的浮点数,该函数的返回值也是基于 random() 的返回值进行调整取得。

import random

for i in xrange(5):
    print '%04.3f' % random.uniform(1, 100),
print
85.481 89.581 34.903 56.218 20.251

随机种子 seeding

每次调用 random() 都将从一个既定序列中返回下一个随机数,该序列很长,即要过很久才会重复。random() 函数的返回值在序列中的起点位置可由一个初始值决定。即可通过 random.seed(hashable_obj) 来初始化伪随机生成器,从而使得该生成器生成一个预期的随机数序列。

seed() 的参数值可为任意可 Hash 的对象,默认是使用与系统平台相关的随机源,如果没有则使用当前时间。

import random

random.seed(1) # 初始化后,每次运行下面的随机数函数都返回相同的随机数。

print 'First time:'
for i in xrange(5):
    print '%04.3f' % random.random(),
print

random.seed(1) # 初始化后,每次运行下面的随机数函数都返回相同的随机数。
print 'Second time:'
for i in xrange(5):
    print '%04.3f' % random.random(),
print
First time:
0.134 0.847 0.764 0.255 0.495
Second time:
0.134 0.847 0.764 0.255 0.495

保存状态及恢复

使用 seed() 可初始化伪随机数序列中的初始位置,而 random() 在伪随机数序列中的当前位置(状态),可通过 getstate()setstate() 进行获取和设置。

import random
import os
import cPickle as pickle

if os.path.exists('state.dat'):
    # Restore the previously saved state
    print 'Found state.dat, initializing random module'
    with open('state.dat', 'rb') as f:
        state = pickle.load(f)
    random.setstate(state)
else:
    # Use a well-know start state
    print 'No state.dat, seeding'
    random.seed(1)
    
# Produce random values
for i in xrange(5):
    print '%04.3f' % random.random(),
print

# Save state for next time
with open('state.dat', 'wb') as f:
    pickle.dump(random.getstate(), f)
    
# Produce more random values
print '\nAfter saving state:'
for i in xrange(5):
    print '%04.3f' % random.random(),
print
Found state.dat, initializing random module
0.449 0.652 0.789 0.094 0.028

After saving state:
0.836 0.433 0.762 0.002 0.445

随机整数

import random

# randint(min, max) 随机返回 [min, max] 间的一个整数
print '[1, 100]',
for i in xrange(3):
    print random.randint(1, 100),
print
    
print '[-5, 5]:',
for i in xrange(3):
    print random.randint(-5, 5),
print

# randrange(start, end, step) 随机挑选 range(start, end, step) 序列中的一个整数
print 'range(0, 101, 5):',
for i in xrange(3):
    print random.randrange(0, 101, 5),
print
[1, 100] 46 29 3
[-5, 5]: 4 1 2
range(0, 101, 5): 15 100 90

在一个序列中随机挑选

choice() 可在一个序列中进行随机选择。

import random

# 模拟抛硬币,统计正反面出现的次数
outcomes = { 'heads': 0, 'tails': 0 }
sides = outcomes.keys()

for i in range(10000):
    outcomes[ random.choice(sides) ] += 1
    
print 'Heads:', outcomes['heads']
print 'Tails:', outcomes['tails']
Heads: 4983
Tails: 5017

洗牌

将一个序列想象成一副牌,shuffle() 实现洗牌功能。

import random

cards = list(range(10))
random.shuffle(cards)
print cards

random.shuffle(cards)
print cards
[6, 2, 1, 3, 0, 8, 4, 9, 7, 5]
[3, 5, 1, 4, 2, 6, 7, 0, 8, 9]

取样

sample() 在不对输入序列进行任何改动的情况下,取抽取出 n 个样本。

import random

with open('/usr/share/dict/words', 'rt') as f:
    words = f.readlines()
words = [w.rstrip() for w in words]

for w in random.sample(words, 5):
    print w
Salton's
mushrooming
immunology
racketed
hundred's

多个随机生成器并行

以上介绍的模块级的函数,实际上都作用在 random 模块内置的一个 Random 实例上的。显式创建多个 Random 实例,在这个实例上的随机数生成过程互不干扰。

import random
import time

print 'Default initialization:\n'
r1 = random.Random()
r2 = random.Random()

for i in xrange(3):
    print '%04.3f %04.3f' % (r1.random(), r2.random())
    
print '\nSame seed:\n'
seed = time.time()
r1 = random.Random(seed)
r2 = random.Random(seed)

for i in xrange(3):
    print '%04.3f %04.3f' % (r1.random(), r2.random())
Default initialization:

0.930 0.448
0.944 0.743
0.872 0.949

Same seed:

0.760 0.760
0.577 0.577
0.559 0.559

如果系统本身对随机数生成支持不好,可能会使用当前时间作为默认种子,从而上面代码的第一部分也可能会输出相同的随机值。

要想确保生成器产生不同的值,可使用 jumpahead(delta) 对初始状态进行偏移,偏移量 delta 是一个非负整数,生成器内部状态会基于这个 delta 值进行偏移,但并不是进行简单的递增该值。

import random

r1 = random.Random()
r2 = random.Random()

# Force r2 to a different part of the random period than r1.
r2.setstate(r1.getstate())
r2.jumpahead(1024)

for i in xrange(3):
    print '%04.3f %04.3f' % (r1.random(), r2.random())
0.914 0.925
0.930 0.333
0.768 0.021

SystemRandom

一些操作系统本身会提供一个随机数生成器,这个生成器具有具有更随机,更强大的功能。random 模块通过 SystemRandom 类对其进行封装。SystemRandom 和 Random 类的 API 是相同的,只不过它是通过 os.urandom() 来产生随机数的。

import random
import time

print 'Default initialization:\n'
r1 = random.SystemRandom()
r2 = random.SystemRandom()

for i in xrange(3):
    print '%04.3f %04.3f' % (r1.random(), r2.random())
    
print '\nSame seed:\n'
seed = time.time()
r1 = random.SystemRandom(seed)
r2 = random.SystemRandom(seed)

for i in xrange(3):
    print '%04.3f %04.3f' % (r1.random(), r2.random())
Default initialization:

0.595 0.363
0.668 0.468
0.819 0.891

Same seed:

0.794 0.695
0.138 0.481
0.900 0.211

由于 SystemRandom 中的随机数是来自系统本身的,而不是基于软件状态产生,因此是不可重复的(即使初始化时 seed 相同也不能产生相同的随机数)。实际上,seed() 和 setstate() 在 SystemRandom 上都是无效的。

非均匀分布

random() 是基于均匀分布算法实现的。 random 模块也实现了基于其它分布实现的随机数生成器。

正态分布 normal distribution

正态分布也叫高斯分布,曲线形如钟。random.normalvariate()random.gauss()(速度较快)能生成基于正态分布的随机值。random.lognormalvariate() 生成的随机值,其对数值是符合正态分布的。

近似分布 approximation distribution

对于小样本环境,用三角分布 triangular distribution 用来表示近似分布,对应函数是 random.triangular()

指数分布 exponential distribution

expovariate()paretovariate()

更多资源

]]>
Ubuntu 重装后的初始化设置 2017-08-04T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/ubuntu_reinstall 更新 apt
$ sudo apt-get update && sudo apt-get upgrade -y

更新 Grub 启动延时

$ sudo vi /etc/default/grub

修改 GRUB_TIMEOUT 项的值。

再运行:

$ sudo update-grub

安装多线程下载工具 axel

$ sudo apt-get install axel

# download
$ axel -n 5 http://example.com/file.gzip

安装 Fcitx 五笔拼音输入法

  1. 安装汉语语言包和 fcitx
$ sudo apt-get install language-pack-zh-hans -y

$ sudo apt-get install fcitx-table-wbpy
  1. 在 “System Settings –> Language Support” 中将 “Keyboard input method system: “ 设置为 “fcitx”。

  2. 在 “System Settings –> Text Entry” 中将添加 WubiPinyin(Fcitx)。将 “English(US)” 和 “WubiPinyin(Fcitx)” 的 “Swith to next source using:” 值都设置为 “Control L”。

  3. 在 WubiPinyin(Fcitx) 的 “Input Method Configuration” 界面中将 Trigger Input Method 清空,关闭 “Enable Hotkey to scroll Between Input Method”。

安装 WPS

下载地址: http://community.wps.cn/download/

ubuntu 16.04 下解决 WPS 无法输入中文的问题:

  1. word 部分

在 /usr/bin/wps 的第一行 #!/bin/bash 下添加:

export XMODIFIERS="@im=fcitx"
export QT_IM_MODULE="fcitx"
  1. ppt、excel部分

和 word 一样的方法添加环境变量,只是编辑的文件各不同:

$ vi /usr/bin/wpp
$ vi /usr/bin/et

安装配置 git

详细文档见 Generating a new SSH key and adding it to the ssh-agent

$ sudo apt-get install git -y  # 安装 git

$ git config --global user.name "Jiang Haiyun" 
$ git config --global user.email "jiang.haiyun@gmail.com"

$ ssh-keygen -t rsa -b 4096 -C "jiang.haiyun@gmail.com" # 创建 ssh key

$ eval "$(ssh-agent -s)"  # 在后台开启 ssh-agent

Agent pid 59566

$ ssh-add ~/.ssh/id_rsa # Add your SSH private key to the ssh-agent

$ sudo apt-get install xclip # Downloads and installs xclip.

$ xclip -sel clip < ~/.ssh/id_rsa.pub # Copies the contents of the id_rsa.pub file to your clipboard

之后,可以将 SSH Key 添加到 GitHub 和 Bitbucket 中。

恢复系统的配置文件

$ cd ~/workspace
$ git clone git@github.com:haiiiiiyun/dot-files.git
$ cp ~/workspace/dot-files/bashrc ~/.bashrc
$ cp ~/workspace/dot-files/tmux.conf ~/.tmux.conf
$ cp ~/workspace/dot-files/vimrc ~/.vimrc
$ cp ~/workspace/dot-files/psqlrc ~/.psqlrc
$ cp -r ~/workspace/dot-files/tmuxinator/ ~/.tmuxinator

安装和配置 VIM

$ sudo apt-get install vim-gnome -y

# Plugin manager Vundle
$ mkdir -p ~/.vim/bundle && \
$ git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim

# 在 VIM 中运行 `:PluginInstall`

安装 tmux 和 Tmuxinator

$ sudo apt-get install tmux

# Tmuxinator基于Ruby,首先安装Ruby
$ sudo apt-get install ruby

# gem 版本需在 2.6.x 以上:

$ sudo gem update --system
$ gem -v
2.6.12

# 配置 gem 的 Ruby China 镜像
$ gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/
$ gem sources -l
https://gems.ruby-china.org

$ sudo gem install tmuxinator

将 “CAPS LOCK” 键设置成 CTRL 键

在 Linux,需对键盘配置文件进行修改:

sudo vi /etc/default/keyboard, 找到以 XKBOPTIONS 开头的行,添加 ctrl:nocaps 使 CAPS LOCK 成为另一个 CTRL 键,或者添加 ctrl:swapcaps 使 CAPS LOCK 键和 CTRL 两键的功能相互交换。

例如,修改后的内容可能为:

XKBOPTIONS="lv3:ralt_alt,compose:menu,ctrl:nocaps"

然后运行:

sudo dpkg-reconfigure keyboard-configuration

重启。

安装 PostgreSQL

  1. 先从 PostgreSQL 下载页 获取相应的 Apt 仓库信息,然后创建文件 /etc/apt/sources.list.d/pgdg.list, 命令为:
$ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
  1. 加载仓库的 GPG Key:
$ wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add -
  1. 安装
$ sudo apt-get update && sudo apt-get install postgresql-9.5 postgresql-contrib -y
$ sudo apt-get install libpq-dev

Python 开发环境

$ sudo apt-get install python-pip -y
$ pip install -U pip
$ sudo apt-get install python-dev

$ sudo pip install virtualenv
$ sudo pip install virtualenvwrapper

# virtual env dirs
$ mkdir ~/.envs
$ . ~/.bashrc  # 运行 source virtualenvwrapper.sh 来初始化

# 创建 xcity 项目的环境
$ mkvirtualenv xcity
$ workon xcity
$ pip install -f ~/workspace/xcity/requirements/local.txt

安装 Jupyter

$ sudo pip install jupyter 

# 安装 Shadowsocks 客户端

```bash
sudo add-apt-repository ppa:hzwhuang/ss-qt5
sudo apt-get update
sudo apt-get install shadowsocks-qt5 -y

在 Ubuntu 16.04 中启动时如果出现错误:

ss-qt5: error while loading shared libraries: libQtShadowsocks.so.1: cannot open shared object file: No such file or directory

可创建链接解决:

$ sudo ln /usr/lib/libQtShadowsocks.so /usr/lib/libQtShadowsocks.so.1

SS 共享账号页见 https://doub.bid/sszhfx/。

安装 Ext JS 6 开发环境

设置 Ext JS 6 开发环境

安装 Docker 和 Docker XAMPP

安装 Docker 见 在 Ubuntu 上安装 Docker

安装 Docker Compose 见 安装 Docker Compose 并运行一个简单的 Python Web 应用

配置阿里云镜像见 Docker 注册中心及配置阿里云加速

或者使用 Docker 中国官方镜像加速,见 https://www.docker-cn.com/registry-mirror。

可以在 Docker 守护进程启动时传入 –registry-mirror 参数:

$ docker --registry-mirror=https://registry.docker-cn.com daemon

为了永久性保留更改,可以修改 /etc/docker/daemon.json 文件并添加上 registry-mirrors 键值。

{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}

修改保存后重启 Docker 以使配置生效。

安装配置 Docker 版本的 XAMPP:

Docker 映像文件见: https://hub.docker.com/r/tomsik68/xampp/ 。

$ docker pull tomsik68/xampp

# 运行
$ docker run --name myXampp -p 9922:22 -p 9980:80 -d -v ~/workspace/www:/www tomsik68/xampp

在 .bashrc 中添加 alias:

# run docker xampp
alias xamppstart='docker run --name myXampp -p 9922:22 -p 9980:80 -d -v ~/workspace/www:/www tomsik68/xampp'

安装 Node 和 Angular 环境

Angualr CLI 要求 node 6.9.x 和 npm 3.x.x。

安装 node

在 Ubuntu 16.04 上安装当前稳定版本 v6.x:

$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
$ sudo apt-get install -y nodejs

详细参考 https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions 。

配置 npm 使用淘宝镜像:

$ npm config set registry=http://registry.npm.taobao.org

详细见 npm 等国内镜像整理

安装 Angualar CLI

$ sudo npm install -g @angular/cli
]]>
使用 Python 字符串的 format 功能实现自定义格式化输出 2017-08-02T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/python-string-format Python V2.6 开始,可用 str.format() 来实现 % 的格式化输出。字符串中的替换区域用 {} 包围,因此如果要输出这两个包围符,需进行转义,如 ``。

基于位置的参数

print '{0} is {1} years old.'.format('haiiiiiyun', 32)

# v2.7 开始可以用 {} 来省略默认的位置序号, '{} {}' 等价于 '{0} {1}'
print '{} is {} years old.'.format('haiiiiiyun', 32)
haiiiiiyun is 32 years old.
haiiiiiyun is 32 years old.

基于关键字参数

适合参数多次输出的情况

print '{name} is {age} years old.'.format(name='haiiiiiyun', age=32)
haiiiiiyun is 32 years old.

基于对象属性值的参数

arg.name 将通过 getattr() 来获取对象的属性值。

class Person:
    def __init__(self, name, age):
        self.name, self.age = name, age
        
haiiiiiyun = Person('haiiiiiyun', 32)

print '{p.name} is {p.age} years old.'.format(p=haiiiiiyun)
haiiiiiyun is 32 years old.

基于下标的参数

arg[index] 将通过 getitem() 来获取参数值。

haiiiiiyun = ['haiiiiiyun', 32]

print '{p[0]} is {p[1]} years old.'.format(p=haiiiiiyun)
haiiiiiyun is 32 years old.

参数值切换

默认在格式化前,每个参数值都通过 format() 方法进行格式处理。现支持指定两种格式处理方式。

  1. !s 在参数值上调用 str()
  2. !r 在参数值上调用 repr()
class Person:
    def __init__(self, name, age):
        self.name, self.age = name, age
        
    def __str__(self):
        return '"{}"'.format(self.name)
    
    def __repr__(self):
        return '<{}>'.format(self.name)
        
haiiiiiyun = Person('haiiiiiyun', 32)

print 'str:'
print '{p!s} is {p.age} years old.'.format(p=haiiiiiyun)

print 'repr:'
print '{p!r} is {p.age} years old.'.format(p=haiiiiiyun)
str:
"haiiiiiyun" is 32 years old.
repr:
<haiiiiiyun> is 32 years old.

格式限定

通用格式形式为 :[[fill]align][sign][#][0][width][,][.precision][type]

align 指定对齐方式,值有:

含义
< 左对齐(默认值)
> 右对齐
= 强制填充符接在符号值(如果有的话)后,在数字值前。该选项只对数字参数有限,用来显示如 +000120 的数字。
^ 居中

: 后面要带一个填充字符,默认为空格符。

print "{0:-<10s} {1:->10s} {2:-^10s}".format("Name", "Arg", "Balance")
print "{0:<10s} {1:>10d} {2:^10d}".format("Haiiiiiyun", 32, 120)
Name------ -------Arg -Balance--
Haiiiiiyun         32    120    

sign 指定数字值符号的显示方式,值有:

含义
+ 正数和负数都显示符号
- 负数显示,正数不显示(默认)
空格 表示正数前用空格占位,负数前显示符号
print "{0:->10s} {1:->10s} {2:->10s} {3:->10s} {4:->10s} {5:->10s}".format("", "", "", "", "", "")
print "{0:>+10d} {1:>+10d} {2:>-10d} {3:>-10d} {4:> 10d} {5:> 10d}".format(120, -120, 120, -120, 120, -120,  )
---------- ---------- ---------- ---------- ---------- ----------
      +120       -120        120       -120        120       -120

# 只作用于整数值,表示在数字前加前缀,比如二进制数字前加 0b, 八进制前加 0o,十六进制前加 0x

, 表示在多位整数中添加千分位。

width 表示数字域的最小长度。width 前加 0 表示进行 0 填充,等价于对齐方式 =

precision 表示浮点数小数点后显示的数字个数。

type 指定显示的类型。字符型的有:

类型 含义
s 字符串,或直接省略不写

整数型的有:

类型 含义
b 二进制。
c char, 在输出前将整数转化成对应的 unicode 字符
d 十进制,这是默认值,可省略
o 八进制
x,X 十六进制
n 类似 d, 但使用本地的显示设置,如插入千分符等

浮点数型的有:

类型 含义
e, E 科学计数法表示
f, F 定点数显示,默认精度是 6
g, G General format. g 是默认值,可省略。
n 类似 g, 但使用本地的显示设置,如插入千分符等
% 数字先乘以 100, 再用 f 格式显示,后加 %
print 'Integer:'
print '{:8b} {:8c} {:8d} {:8o} {:8x}'.format(65, 65, 65, 65, 65)
print '{:#8b} {:#8c} {:#8d} {:#8o} {:#8x}'.format(65, 65, 65, 65, 65)
Integer:
 1000001        A       65      101       41
0b1000001        A       65    0o101     0x41

参考

]]>
Python 2 标准库示例:5.2 fractions-有理数 2017-07-27T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/Python2Lib-math-fractions 目的: 实现有理数。

Python 版本: 2.6+。

Fraction 类实现了由 numbers.Rational 模块中定义的有理数运算 API.

创建 Fraction 实例

实例化后,Fraction 实例都将数规整化成分子(numerator) 和分母(denominator) 两个整数。

import fractions
import decimal

# 1. 通过指定分子(numerator) 和分母(denominator) 来实例化
print 'from (numerator, denominator):'
for n, d in [(1, 2), (2, 4), (3, 6)]:
    f = fractions.Fraction(n, d)
    print '%s/%s = %s' %(n, d, f)
    
print
print 'from string "X/Y":'
# 2. 以分数字符串的形式指定
for s in ['1/2', '  2/4 ', '-3/6']:
    f = fractions.Fraction(s)
    print '%s = %s' % (s, f)
    
print
print 'from string "X.Y":'
# 3. 字符串还可以小数的形式指定
for s in ['0.5', '1.5', '2.0']:
    f = fractions.Fraction(s)
    print '%s = %s' % (s, f)
    
print
print 'from float:'
# 4. 以浮点数指定(v2.7+), v2.7 之前要用 Fraction.from_float() 实现
for v in [0.5, 1.5, 2.0]:
    f = fractions.Fraction(v)
    print '%s = %s' % (v, f)

# 有些 float 无法精确转换成 Fraction, 会出现未期望的结果
print
print 'from float 0.1:'
# 4. 以浮点数指定(v2.7+), v2.7 之前要用 Fraction.from_float() 实现
v = 0.1
f = fractions.Fraction(v)
print '%s = %s' % (v, f)

print
print 'from decimal:'
# 5. 以 Decimal 指定(v2.7+), v2.7 之前要用 Fraction.from_decimal() 实现,
#  和 float 的情况不同,Decimal 没有浮点数的精度问题
for v in [decimal.Decimal('0.1'),
          decimal.Decimal('0.5'),
          decimal.Decimal('1.5'),
          decimal.Decimal('2.0')]:
    f = fractions.Fraction(v)
    print '%s = %s' % (v, f)
from (numerator, denominator):
1/2 = 1/2
2/4 = 1/2
3/6 = 1/2

from string "X/Y":
1/2 = 1/2
  2/4  = 1/2
-3/6 = -1/2

from string "X.Y":
0.5 = 1/2
1.5 = 3/2
2.0 = 2

from float:
0.5 = 1/2
1.5 = 3/2
2.0 = 2

from float 0.1:
0.1 = 3602879701896397/36028797018963968

from decimal:
0.1 = 1/10
0.5 = 1/2
1.5 = 3/2
2.0 = 2

运算

Fraction 实例可以在任何的数学表达式中使用。

import fractions

f1 = fractions.Fraction(1, 2)
f2 = fractions.Fraction(3, 4)

print '%s + %s = %s' % (f1, f2, f1+f2)
print '%s - %s = %s' % (f1, f2, f1-f2)
print '%s * %s = %s' % (f1, f2, f1*f2)
print '%s / %s = %s' % (f1, f2, f1/f2)
1/2 + 3/4 = 5/4
1/2 - 3/4 = -1/4
1/2 * 3/4 = 3/8
1/2 / 3/4 = 2/3

近似值

可利用 Fraction 将一个浮点数转换到一个最接近的有理数值。

import fractions
import math

print 'PI = ', math.pi
f_pi = fractions.Fraction(str(math.pi))
print 'No limit = ', f_pi

for i in [1, 6, 11, 60, 70, 90, 100]:
    limited = f_pi.limit_denominator(i) # 限制转换后的分母的最大值
    print '{0:8} = {1}'.format(i, limited)
PI =  3.14159265359
No limit =  314159265359/100000000000
       1 = 3
       6 = 19/6
      11 = 22/7
      60 = 179/57
      70 = 201/64
      90 = 267/85
     100 = 311/99

找到最大公约数

fractions.gcd(a, b) 可用来找到最大公约数。

import fractions

# 非 0 时:
print 'gcd(15, 25) = ', fractions.gcd(15, 25)

# b 非 0 时结果取 b 的符号
print 'gcd(15, -25) = ', fractions.gcd(15, -25)

# b  0 时结果取 a 的符号
print 'gcd(-15, 0) = ', fractions.gcd(-15, 0)
gcd(15, 25) =  5
gcd(15, -25) =  -5
gcd(-15, 0) =  -15

更多资源

]]>
Python 2 标准库示例:5.1 decimal-定点和浮点数学 2017-07-27T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/Python2Lib-math-decimal 目的: 使用定点或浮点数进行十进制运算。

Python 版本: 2.4+。

decimal 模块为十进制浮点数运算提供了支持。相比 float 数据类型有如下优势:

  • decimal 是基于浮点模型的, 但设计时以人为本, 实现的浮点运算以普通人熟悉的方式进行,而不是程序员的 IEEE 浮点数的样子。
  • decimal 数可以精确表示。而像 1.1 或 2.2 等浮点数却不能用二进制浮点数精确表示。
  • 精度在运算中也会保留,因此, Decimal 数运算 0.1+0.1+0.1-0.3 的结果会是 0,但在二进制浮点数运算中, 结果会是 5.5511151231257827e-017。
  • decimal 模块引入了有效位的概念,因此 1.30+1.20 的值是 2.50, 末尾的 0 会被保留用来表明有效位,类似的,相乘时两个乘数的有效位也会被保留,例如 1.3*1.21.561.30*1.201.5600
  • 与基于硬件实现的二进制浮点数不同, decimal 模块的精度(默认是 28 位)是可定制的。
  • 内置的 float 类型只导出了部分的浮点数功能,而 decimal 模块实现了浮点数的所有功能,程序员可以完全控制舍入(rounding), 信号量处理(signal handling)等。

该模块的设计基于 3 个概念:十进制数,运算上下文和信号。

十进制数

十进制数值用 Decimal 类的实例表示。

import decimal

fmt = '{0:<25} {1:<25}'
print fmt.format('Input', 'Output')
print fmt.format('-' * 25, '-' * 25)

# 1. 没有参数时实例化返回 Decimal('0')
print fmt.format('', decimal.Decimal())

# 2. 用整数作为参数来实例化
print fmt.format(5, decimal.Decimal(5))

# 3. 用字符串作为参数来实例化
print fmt.format('3.14', decimal.Decimal('3.14'))

# 4. 从 v2.6+ 开始,字符串前后允许有空白字符
print fmt.format('  -3.14  ', decimal.Decimal('  -3.14  '))

# 5. 从 v2.7+ 开始,允许直接舍入 float 值进行实例化,参数的二进制浮点值将无损转换到十进制值,
# 无法精确表示的二进制浮点数,转换后会舍入。
print fmt.format(1.1, decimal.Decimal(1.1))

# 6. 之前的版本,要先将 float 转换成字符串再传处, 或者通过 Decimal.from_float() 进行
f = 1.1
print fmt.format(repr(f), decimal.Decimal(str(f)))
print fmt.format('%.23g' %f, str(decimal.Decimal.from_float(f))[:25])

# 7. 通过舍入 tuple (sign, (digit1, digit2, ...), exponent) 来实例化, 其中 sign 为 0 时表示正数, 1 时为负数,
#    (digit1, digit2, ...) 表示出现的数字,而 exponent 是个整数的指数
print fmt.format((1, (3, 1, 4), -2), decimal.Decimal((1, (3, 1, 4), -2)))
Input                     Output                   
------------------------- -------------------------
                          0                        
5                         5                        
3.14                      3.14                     
  -3.14                   -3.14                    
1.1                       1.100000000000000088817841970012523233890533447265625
1.1                       1.1                      
1.1000000000000000888178  1.10000000000000008881784
(1, (3, 1, 4), -2)        -3.14                    

基于 tuple 的表示不便于用来实例化,但可用来导出 Decimal 值的无损精度表示,也可用 tuple 的形式在网络上传输,或保存在数据库中。

运算

Decimal 实例的运算与内置的数字类型的运算相似。

import decimal

a = decimal.Decimal('5.1')
b = decimal.Decimal('3.14')
c = 4
d = 3.14

print 'a=', repr(a)
print 'b=', repr(b)
print 'c=', repr(c)
print 'd=', repr(d)
print

print 'a+b=', a+b
print 'a-b=', a-b
print 'a*b=', a*b
print 'a/b=', a/b
print

print 'a+c=', a+c
print 'a-c=', a-c
print 'a*c=', a*c
print 'a/c=', a/c

print 'a+d=', 
try:
    print a+d
except TypeError, e:
    print e
a= Decimal('5.1')
b= Decimal('3.14')
c= 4
d= 3.14

a+b= 8.24
a-b= 1.96
a*b= 16.014
a/b= 1.624203821656050955414012739

a+c= 9.1
a-c= 1.1
a*c= 20.4
a/c= 1.275
a+d= unsupported operand type(s) for +: 'Decimal' and 'float'

Decimal 数可与整数相互运算,但是浮点数必须先转换为 Decimal 实例后才能相互运算。

除了基本运算外,Decimal 实例还有获取基于 10 或自然数的 log 值的函数,如:

import decimal

print 'decimal.Decimal(100).log10()=', decimal.Decimal(100).log10()
print 'decimal.Decimal(100).ln()=', decimal.Decimal(100).ln()
decimal.Decimal(100).log10()= 2
decimal.Decimal(100).ln()= 4.605170185988091368035982909

特殊值

Decimal 可表示一些特殊值,如正负无穷大, NaN,和 0 等。

import decimal

for value in ['Infinity', 'NaN', '0']:
    print decimal.Decimal(value), decimal.Decimal('-' + value)
print

# 与无穷大的运算结果都为无穷大
print 'Infinity + 1:', (decimal.Decimal('Infinity') + 1)
print '-Infinity + 1:', (decimal.Decimal('-Infinity') +1)

# 与 NaN 的比较结果都是 False
print decimal.Decimal('NaN') == decimal.Decimal('Infinity')
print decimal.Decimal('NaN') != decimal.Decimal(1)
Infinity -Infinity
NaN -NaN
0 -0

Infinity + 1: Infinity
-Infinity + 1: -Infinity
False
True

上下文 Context

上面的所有例子都使用了 decimal 模块的模块行为。不过通过 context,可以定制精度、舍入、错误处理等设置。context 可应用于一个线程中或者某代码段中的所有 Decimal 实例。

当前上下文

获取当前的全局上下文使用 getcontext()

import decimal
import pprint

context = decimal.getcontext()

# 允许的最大和最小指数值(整数)
print 'Emax =', context.Emax
print 'Emin =', context.Emin

# capitals 域的值为 0 或 1 (默认值).
# 值为 1 时,指数打印输出时用 E 表示,否则用小写 e 表示,如 Decimal('6.02e+23').
print 'capitals =', context.capitals

# 精度
print 'prec =', context.prec

# 舍入
print 'rounding =', context.rounding

print 'flags ='
pprint.pprint(context.flags)

print 'traps ='
pprint.pprint(context.traps)
Emax = 999999999
Emin = -999999999
capitals = 1
prec = 28
rounding = ROUND_HALF_EVEN
flags =
{<class 'decimal.Clamped'>: 0,
 <class 'decimal.InvalidOperation'>: 0,
 <class 'decimal.DivisionByZero'>: 0,
 <class 'decimal.Inexact'>: 1,
 <class 'decimal.Rounded'>: 1,
 <class 'decimal.Subnormal'>: 0,
 <class 'decimal.Overflow'>: 0,
 <class 'decimal.Underflow'>: 0}
traps =
{<class 'decimal.Clamped'>: 0,
 <class 'decimal.InvalidOperation'>: 1,
 <class 'decimal.DivisionByZero'>: 1,
 <class 'decimal.Inexact'>: 0,
 <class 'decimal.Rounded'>: 0,
 <class 'decimal.Subnormal'>: 0,
 <class 'decimal.Overflow'>: 1,
 <class 'decimal.Underflow'>: 0}

精度

上下文中的 prec 属性控制运算时生成的新值的精度(即非零有效数字)。明确实例化的值不受此控制。

import decimal

d = decimal.Decimal('0.123456')
for i in range(4):
    decimal.getcontext().prec = i
    print i, ':', d, d * 1
0 : 0.123456 0
1 : 0.123456 0.1
2 : 0.123456 0.12
3 : 0.123456 0.123

舍入

由于存在精度限制,故有舍入。下面是支持的舍入方式。

  • ROUND_CEILING: 向上舍入。
  • ROUND_FLOOR: 向下舍入。
  • ROUND_DOWN: 向零舍入。
  • ROUND_HALF_DOWN: 当最低有效位小于或等于 5 时,向零舍入;否则偏离零方向舍入。
  • ROUND_HALF_EVEN: 类似 ROUND_HALF_DOWN,但当最低有效位为 5 时,进行向偶数舍入,即此时当前一个数字是偶数时向下舍入,前一个数字是奇数是向上舍入。
  • ROUND_HALF_UP: 类似 ROUND_HALF_DOWN,但当最低有效位为 5 时,进行偏离零方向舍入。
  • ROUND_05UP: 如果最低有效位之前的数字是 0 或 5 时,进行偏离零方向舍入, 否则向零舍入。
import decimal
 
context = decimal.getcontext()

ROUNDING_MODES = [
    'ROUND_CEILING',
    'ROUND_FLOOR',
    'ROUND_DOWN',
    'ROUND_HALF_DOWN',
    'ROUND_HALF_EVEN',
    'ROUND_HALF_UP',
    'ROUND_UP',
    'ROUND_05UP',
]


values = ['0.123', '0.125', '0.127', '-0.133', '-0.135', '-0.137']
header_fmt = '{:10} ' + ' '.join(['{:^8}'] * 6)
print header_fmt.format(' ', *values )

for rounding_mode in ROUNDING_MODES:
    print '{0:10}'.format(rounding_mode.partition('_')[-1]),
    for v in values:
        context.rounding = getattr(decimal, rounding_mode)
        context.prec = 2
        value = decimal.Decimal(v) * 1
        print '{0:^8}'.format(value),
    print
            0.123    0.125    0.127    -0.133   -0.135   -0.137 
CEILING      0.13     0.13     0.13    -0.13    -0.13    -0.13  
FLOOR        0.12     0.12     0.12    -0.14    -0.14    -0.14  
DOWN         0.12     0.12     0.12    -0.13    -0.13    -0.13  
HALF_DOWN    0.12     0.12     0.13    -0.13    -0.13    -0.14  
HALF_EVEN    0.12     0.12     0.13    -0.13    -0.14    -0.14  
HALF_UP      0.12     0.13     0.13    -0.13    -0.14    -0.14  
UP           0.13     0.13     0.13    -0.14    -0.14    -0.14  
05UP         0.12     0.12     0.12    -0.13    -0.13    -0.13  

局部上下文

v2.5+ 后可以通过 with 语句将 context 应用于代码段。

import decimal

decimal.getcontext().prec = 28

print 'Default precision:', decimal.getcontext().prec
print '3.14 / 3 = ', (decimal.Decimal('3.14') / 3)

print

with decimal.localcontext() as c:
    c.prec = 2
    print 'Local precision:', c.prec
    print '3.14 / 3 = ', (decimal.Decimal('3.14') / 3)
    
print
print 'Default precision:', decimal.getcontext().prec
print '3.14 / 3 = ', (decimal.Decimal('3.14') / 3)
Default precision: 28
3.14 / 3 =  1.046666666666666666666666666

Local precision: 2
3.14 / 3 =  1.1

Default precision: 28
3.14 / 3 =  1.046666666666666666666666666

预生成 Context 实例

Context 实例可用来创建 Decimal 实现, 创建时会使用该 Conctext 实例中的设置信息。

import decimal

# Set up a context with limited precision
c = decimal.getcontext().copy()
c.prec = 3

# Create our constant
pi = c.create_decimal('3.1415')

# The constant value is rounded off
print 'PI:', pi

# The result of using the constant uses the global context
print 'RESULT:', decimal.Decimal('2.01') * pi
PI: 3.14
RESULT: 6.3114

线程

global 的上下文,实际上是针对每个线程的, 即每个线程都有自己独立的 global default context。

import decimal
import threading
from Queue import PriorityQueue

class Multiplier(threading.Thread):
    def __init__(self, a, b, prec, q):
        self.a = a
        self.b = b
        self.prec = prec
        self.q = q
        threading.Thread.__init__(self)
        
    def run(self):
        c = decimal.getcontext().copy()
        c.prec = self.prec
        decimal.setcontext(c) # set own global default context
        self.q.put( (self.prec, a * b) )
        return
    
a = decimal.Decimal('3.14')
b = decimal.Decimal('1.234')

# A PrioriyQueue will return values sorted by precision, no matter
# what order the threads finish.
q = PriorityQueue()
threads = [Multiplier(a, b, i, q) for i in range(1, 6)]
for t in threads:
    t.start()
    
for t in threads:
    t.join()
    
for i in range(5):
    prec, value = q.get()
    print prec, '\t', value
1 	4
2 	3.9
3 	3.87
4 	3.875
5 	3.8748

更多资源

]]>
CSAPP3 2.4 浮点数 2017-07-26T00:00:00+08:00 Haiiiiiyun haiiiiiyun.github.io/CSAPP3-2.4_floating_point 浮点表示用来对形如 $V=x \times 2^n$ 的有理数进行编码。这里讨论的是 IEEE 标准 754 制订的浮点数及其运算标准。

二进制小数

例如这样的一个二进制小数 $b$: $b_mb_{m-1}\cdots b_1b_0.b_{-1}b_{-2}\cdots b_{-n-1}b_{-n}$(小数点右边有 m 个数,左边有 n 个数),数 b 定义为

二进制小数

例如 $101.11_2$ 表示数字 $1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 + 1 \times 2^{-1} + 1 \times 2^{-2} = 5 \frac {3} {4}$

可见,二进制小数点向左移动一位相当于这个数被 2 除(相当于整数的右移),而小数点向右移动一位相当于该数乘以 2。

而开始 $0.11\cdots1_2$ 的数表示的是刚好小于 1 的数。例如 $0.111111_2$ 表示 $\frac {63} {64}$(当小数点右边有 n 个 1时,分母是 $2^n$,分子比分母小 1),这个数值可用简单的表达式 $1.0-\varepsilon$ 表示。

当用有限长度编码时,十进制表示法不同准确表达像 $\frac 1 3$ 这样的数,类似地,小数的二进制表示法只能表示那些能被写成 $x \times 2^y$ 的数,其它的只能进行近似表示。例如 $\frac 1 5$ 可用十进制小数 0.2 精确表示,但只能用十进制小数近似表示。

0.2 的二进制小数表示

练习题 2.45 p113

填写下表中缺失的信息:

小数值 十进制表示 十进制表示
$\frac 1 8$ 0.001 0.125
$\frac 3 4$ 0.11 0.75
$\frac {25} {16}$ 1.1001 1.5625
$\frac {43} {16}$ 10.1011 2.6875
$\frac 9 8$ 1.001 1.125
$\frac {47} 8$ 101.111 5.875
$\frac {51} {16}$ 11.0011 3.1875

当将十进制小数转成分数时,可进用减法分解,例如 3.1875 = 3 + 0.125 + 0.0625 = 3 + 1/8 + 1/16。

其中各 $2^{-n}$ 表示的十进制数如下表:

$2^{-n}$ 十进制值
$\frac 1 2$ 0.5
$\frac 1 4$ 0.25
$\frac 1 8$ 0.125
$\frac 1 {16}$ 0.0625
$\frac 1 {32}$ 0.03125
$\frac 1 {64}$ 0.015625

** 练习题 2.46 p113**

浮点运算的不精确性能够产生空难性的后果.

1/10 的十进制表达式为一个无穷序列 $0.000110011[0011]\cdots_2$, 当用有限位来表示时, 只能截断为近似值. 例如, 当用 x 值来近似表示 0.1 时, x 只考虑这个序列的二进制小数点右边的前 23 位: x=0.00011001100110011001100.

A. 0.1-x 的二进制表示是什么? 为 $0.000110011[0011]\cdots_2$ - 0.00011001100110011001100 = $0.00\cdots 0_{共23个}[1100]\cdots$.

B. 0.1-x 的近似的十进制数值是多少? 由于 0.1 的二进制为 $0.000[1100]\cdots$, 故 0.1-x 相当于 0.1 的小数点左移 20 位,即值为 $0.1 * \frac 1 {2^{20}}$

C. 100 小时为 $100 \times 3600 = 360000$ 秒, 时间差为 $36000 * 10 * 0.1 \frac 1 {2^{20}}$ =0.3433 秒.

D. 当速度为 2000m/s 时, 偏差为 2000 * 0.3433=687m

IEEE 浮点表示

该标准用 $V = (-1)^s \times M \times 2^E$ 的形式来表示一个数:

  • 符号(sign): $s$ 决定是正数(s=0) 还是负数(s=1),而对于数值 0 的符号解释又作特殊处理.
  • 尾数(significand): $M$ 是一个二进制小数,范围为 [1, $2-\varepsilon$) 或 [0, $1-\varepsilon$).
  • 阶码(exponent): $E$ 的作为是对浮点数加权,权重是 2 的 E 次幂(可能为负数).

因此,浮点数的位表示分为了三个字段, 分别对 s, M, E 进行编码:

  • 最高位直接用来编码符号位 $s$
  • $k$ 位阶码字段 $exp=e_{k-1}\cdots e_1e_0$ 编码阶码 $E$
  • $n$ 位小数字段 $frac=f_{n-1}\cdots f_1f_0$ 编码尾数 $M$,但是编码出来的值还要依赖于阶码字段的值是否为 0 进行不同解释.

IEEE 小点数的 2 个字段表示

在 C 语言中, 单精度 float 中, s, exp, frac 字段分别为 s=1 位, k=8 位, n=23 位 (共 32 位). 而 double 中, s=1 位, k=11 位, n=52 位(共 64 位).

给定位表示, 根据 exp 值, 被编码的值可以分成三种不同的情况(最后一种情况有两个变种).

单精度浮点数值的分类, 规格化, 非规格化和特殊值

情况1 : 规格化的值(用于表示普通的数)

此时 exp 的位模式即不全为 0 (数值 0), 也不全为 1(单精度数值为 $2^8-1=255$,双精度数值为 $2^{11}-1=2047$). 在这种情况中,阶码字段被解释为以偏置(biased) 形式表示的有符号数, 即阶码的值为 $E=e-Bias$, 其中 $e$ 是无符号数,其位表示为 $e_{k-1}\cdots e_1e_0$, 而 Bias 是一个等于 $2^{k-1}-1$(单精度是 127, 双精度是 1023) 的偏置值. 由此产生指数的取值范围是: 单精度 [1-127, 254-127] 即为 [-126, 127],双精度为 [1-1023, 2046-1023] 即为 [-1022, 1023].

小数字段 frac 被解释为描述小数值 f,其中 $0 \leq f < 1$,其二进制表示为 $0.f_{n-1}\cdots f_1f_0$,也就是二进制小数点在最高有效位的左边. 而尾数定义为 $M=1+f$, 这种方式也叫做隐含的以 1 开头的 (implied leading 1) 表示, 因此可以把 $M$ 看成一个二进制表达式为 $1.f_{n-1}\cdots f_1f_0$ 的数字. 用这种方式可以额外地获得一个精度位.

情况2: 非规格化的值 (用于表示很小的数)

此时阶码域为全 0. 这时, 阶码值为 $E=1-Bias$, 这里阶码值不直接定义为 -Bias 是为了从非规格化值能平滑转换到规格化的值. 而尾数的值为 M=f, 也就是小数字段的值,不包含隐含的开头的 1.

非规格化数的 2 个用途:

  1. 表示数值 0: 因为规格化数时, M 隐含有 1, 故总大于等于 1, 不能表示 0. 实际上, +0.0 的浮点表示的位模式为全 0 : 符号位为 0, 阶码字段全 0(表示是非规格化值), 小数域也全 M = f = 0. 而当符号位为 1, 其它位都为 0 时, 得到 -0.0.

  2. 表示那些非常接近于 0.0 的数.

情况3: 特殊值(无穷大和 NaN)

此时,阶码为全 1.

  • 小数域全 0 时, 当 s=0 时表示 $+\infty$, s = 1 时表示 $-\infty$. 无穷能够表示溢出的结果.
  • 小数域非全 0 时, 表示 NaN

数字示例

下图展示了一组数值,它们可以用假定的 6 位格式来表示, 有 k=3 的阶码位和 n=2 的尾数位. 偏置量 Bias = $2^{k-1}-1$ = 3. 图中的 a 部分显示了所有可表示的值(除了 NaN), 两个无穷值在两个末端. 最大数量值的规格化数是 $\pm 14$. 非规格化数聚焦在 0 的附近. 图的 b 部分, 只展示了介于 -1.0 和 1.0 之间的数值. 两个 0 是特殊的非规格化数.

可以观察到, 可表示的数并不是均匀分布的: 越靠近原点处越稠密.

浮点数的范围与分布

下图展示了假定的 8 位浮点格式的示例, 其中阶码位 k=4, 小数位 n=3, 偏置量 Bias = $2^{k-1}-1$=7. 图被分成了三个区域, 用来描述三类数字.

第一部分的阶码部分都为 0, 表示的是非规格化数, 这些数的指数 E = 1-Bias=-6, 得到权 $2^E$=1/64. 小数 $f$ 的范围是 $0, \frac 1 8, \cdots, \frac 7 8$, 从而得到非规格化数 V 的范围是 $[0 \times \frac 1 {64}, \frac 7 8 \times \frac 1 {64}]$, 即为 $[0, \frac 7 {512}]$. 这些非规格化数表示的是 0.0 即原点附近的数.

第二部分表示规格化数. 最小规格化数的阶码 e = 1, 指数 E = e-Bias = -6, 从而与非规格化数平滑过渡. 小数取值范围两样是 $0, \frac 1 8, \cdots, \frac 7 8$, 但是尾数根据定义都还要加 1, 即范围为$[1+0, 1+\frac 7 8]$ 即 $[0, \frac {15} 8]$ 之间. 指数的范围是 [1-7, 14-7] 即 [-6, 7] 之间. 从而数 V 的范围在 $2^{-6} \times 1 = \frac 1 {64}$ 和 $2^7 \times 1\frac 7 8 = 240$ 之间. 超过 240 这个值就会溢出到 $+\infty$.

从图中可看出这样的属性, 假如将上图中的值的位表达式解释为无符号整数, 它们就是按升序排序的. IEEE 进行这样设计, 使得浮点数能使用整数的排序函数.

8位浮点数示例

练习题 2.47 p117

一种基于 IEEE 浮点格式的 5 位浮点表示, 1 位 符号位, 2 位阶码位(k=2), 2 小数位(n=2), 阶码偏置量 B = $2^{k-1}-1$=1, 补全下表.

类型 e E $2^E$ f M $2^E \times M$ V 十进制
0 00 00 非规格化数 0 0 1 0/4 0/4 0 0 0.0
0 00 01 非规格化数 0 0 1 1/4 1/4 1/4 1/4 0.25
0 00 10 非规格化数 0 0 1 2/4 1/2 1/2 1/2 0.5
0 00 11 非规格化数 0 0 1 3/4 3/4 3/4 3/4 0.75
0 01 00 规格化数 1 0 1 0/4 4/4 4/4 1 1.0
0 01 01 规格化数 1 0 1 1/4 5/4 5/4 5/4 1.25
0 01 10 规格化数 1 0 1 2/4 6/4 6/4 3/2 1.5
0 01 11 规格化数 1 0 1 3/4 7/4 7/4 7/4 1.75
0 10 00 规格化数 2 1 2 0/4 4/4 8/4 2 2.0
0 10 01 规格化数 2 1 2 1/4 5/4 10/4 5/2 2.5
0 10 10 规格化数 2 1 2 2/4 6/4 12/4 3 3.0
0 10 11 规格化数 2 1 2 3/4 7/4 14/4 7/2 3.5
0 11 00 正无穷大 - - - - - - $+\infty$ -
0 11 01 NaN - - - - - - NaN -
0 11 10 NaN - - - - - - NaN -
0 11 11 NaN - - - - - - NaN -

下图是非负浮点数的示例:

非负浮点数的示例

  • 值 +0.0 总有一个全为 0 的位表示.
  • 最小的正非规格化值的位表示, 是由最低有效位为 1 而其它所有位为 0 构成的. 它的小数(和尾数)值 $M=f=2^{-n}$, 阶码值 $E=1-(2^{k-1}-1)=-2^{k-1}+2$. 因此它的数字值为 $V=M \times 2^E = 2^{-n-2^{k-1}+2}$.
  • 最大的正非规格化值的位表示,是由全为 1 的小数字段和全为 0 的阶码字段组成的. 它的小数(和尾数)值 $M=f=1-2^{-n}=1-\varepsilon$, 阶码值 $E=1-(2^{k-1}-1)=-2^{k-1}+2$. 因此它的数字值为 $V=M \times 2^E = (1-2^{-n}) \times 2^{-n-2^{k-1}+2}$, 它只比最小规格化值小一点.
  • 最小的正规格化值的位模式的阶码字段的最低有效位为 1, 其它位全为 0. 它的尾数 M=1+0=1, 阶码值 $E=e-Bias=1-Bias=1-(2^{k-1}-1)=-2^{k-1}+2$. 因此它的数字值为 $V=2^{-n-2^{k-1}+2}$.
  • 值 1.0 位表示的阶码字段除了最高有效位为 1 外其它位于都为 0, 其它位也都为 0. 它的尾数值 M=1+0=1, 阶码值 $E=e-Bias=0$. 数值超过 1.0 后, 数值分布间隔将越来越大.
  • 最大的规格化值的位表示的符号位为 0, 阶码的最低有效位为 0, 其它位全为 1. 它的小数值 $f=1-2^{-n}$, 尾数值 $M=1+f=2-2^{-n}=2 - \varepsilon$. 它的阶码值 $E=e-Bias=(2^k-1)-(2^{k-1}-1)=2^{k-1}-1$, 数值为 $V=(2-2^{-n}) \times 2^{2^{k-1}-1}=(1-2^{-n-1}) \times 2^{2^{k-1}}$.

练习题 2.48 p119

整数 3 510 593 的十六进制表示为 0x00359141, 而单精度浮点数 3510593.0 的十六进制表示为 0x4A564504. 推导出这个浮点数表示, 并解释整数和浮点数表示的位之间的关系.

整数 0x00359141 = 0000 0000 0011 0101 1001 0001 0100 0001, 转换成浮点表示时, 先将小数点放在值为 1 的最高有效位的右边, 得到 1.1 0101 1001 0001 0100 0001, 因此, 尾数值就是 $1.1 0101 1001 0001 0100 0001_2$, 而由于规格化数的尾数值是隐含 1 的, 故小数部分就是 $0.1 0101 1001 0001 0100 0001_2$.

单精度浮点数中, 符号位 1 位, 阶码位数 k=8, 小数位 n=23 位. 偏置量 Bias = $2^{k-1}-1=2^7-1=127$

小数部分右边补 0, 得到 23 位的小数部分为 1 0101 1001 0001 0100 0001 00, 定位小数点时共向左移位了 21 位, 从而得知指数值 E = 21, E = e-B, 从而 e= 21+127=148, 从而阶码的位表示为 10010100, 加上符号位 0, 从而得出 3510593.0 的浮点表示为 0 10010100 101011001000101000001 00,

而 0x4a564504 的位模式刚好为 0 10010100 101 0110 0100 0101 0000 0100

练习题 2.49 p120

A. 对于一种具有 n 位小数的浮点格式, 给出不能准确描述的最小正整数的公式(因为要想准确表示它它需要 n+1位小数). 假设阶码字段长度 k 足够大, 可以表示的阶码范围不会限制这个问题.

该练习有助于我们思考什么数不能用浮点数精确表示,因为阶码范围不限制, 故只需考虑小数部分最小能表示的数即可. 小数最多 n 位, 故最小能表示的小数 f 是 n 个 0 后面再加一个 1, 这个小数需要 n+1 个位来表示, 那么尾数 M = f+1 = $1.0000\cdots 01_2$, 即要示的数的二进制表示为:1 后面跟 n 个 0, 再跟一个 1, 值为 $2^{n+1} + 1$

B. 对于单精度格式 n=23, 这个数为 $2^24+1=16777217

舍入

浮点数不同表示所有的数, 故浮点运算只能近似地表示实数运算.

IEEE 浮点数共定义有 4 种舍入方式.

  • 向偶数舍入(round-to-even), 也被称为向最接近的值舍入(round-to-nearest), 这是默认的方式. 这种方式是: 它将数字向上或向下舍入, 使得结果的最低有效数字是偶数, 从而计算平均数时不引入统计偏差.
  • 向零舍入: 正数向下舍入, 负数向上舍入, 它使结果的绝对值变小.
  • 向下舍入: 正数和负数都向下舍入, 从而计算的平均值会偏小.
  • 向上舍入: 正数和负数都向上舍入,从而计算的平均值会偏大.

对于要舍入为整数的十进制来说,向偶数舍入只有当形如 XYZ.500…的才会进行(XYZ是任意数),因为只有这时才表示在两个可能的结果的正中间的值,此时向偶数舍入偏向于使舍入位为偶数,例如 1.5 舍入为 2, 2.5舍入为 2, -1.5 舍入到 -2.

下面是不同舍入方式下舍入到整数的示例:

方式 1.40 1.60 1.50 2.50 -1.50
向偶数舍入 1 2 2 2 -2
向零舍入 1 1 1 2 -1
向下舍入 1 1 1 2 -2
向上舍入 2 2 2 3 -1

类似地, 向偶数舍入法运用在二进制小数上时, 可认为最低有效位的值 0 为偶数, 1 为奇数, 同样只对形如 $XX\cdots X.YY\cdots Y100\cdots$ 的二进制位模板的数才起作用.

练习题 2.50 p120

用向偶数舍入法对下面各数舍入到小数点右边 1 位, 并给出舍入前后的数字值.

A. $10.010_2$, 值为 2.25, 舍入到 $10.0_2$, 值为 2.0

B. $10.011_2$, 值为 2.375,舍入到 $10.1_2$, 值为 2.5

C. $10.110_2$, 值为 2.75, 舍入到 $11.0$, 值为 3.0

D. $11.001_2$, 值为 3.125, 舍入到 $11.0$, 值为 3.0

练习题 2.51 p120

练习题 2.46 中导弹软件将 0.1 近似表示为 $x= 0.000 1100 1100 1100 1100 1100_2$. 假设使用 IEEE 舍入到偶数方式来确定 0.1 的二进制小数点右边 23 位的近似表示 $x’$

A. $x’$ 的二进制表示是什么? 由于 0.1 的二进制为 $0.000 [1100]\cdots_2], 舍入后$x’= 0.000 1100 1100 1100 1100 1101_2$, 它比 0.1 大一点.

B. $x’-0.1$ 的十进制表示的近似值为什么? 二进制为 $0.000 1100 1100 1100 1100 1101_2 - 0.000 1100 1100 1100 1100 1100 [1100]\cdots$ = $0.0\cdots _{加前面共22个0} 000[1100]\cdots$, 值约为 $0.1 * \frac 1 {2^{22}}$

C. 运行 1000 小时后,时针值偏差: $100 * 3600 * 10 * 0.1 * \frac 1 {2^{22}}, 约为 0.0858

D. 导弹位置预测偏差:0.0858 * 2000 = 171

练习题 2.52 p120

考虑下面基于 IEEE 浮点格式的 7 位浮点表示. 两个格式都没有符号位, 它们只能表示非负的数字.

  1. 格式 A: 
    • 阶码位 k=3, 阶码偏置值 Bias = $2^{k-1}-1$ = 3
    • 小数位 n=4
  2. 格式 B:
    • 阶码位 k=4, 阶码偏置值 Bias = $2^{k-1}-1$ = 7
    • 小数位 n=3

将 A 格式的值转换到最接近的 B 格式的值, 必要时用舍入到偶数的方式.

转换时, 阶码和小数分别进行转换, 小数转换时要进行舍入. 当从非规格化转换到规格化数时, 要特殊处理.

格式 A 位 格式 A 值 格式 B 位 格式 B 值
011 0000 1 0111 000 1
101 1110 15/2 1001 111 15/2
010 1001 25/32 0110 100 3/4 向下舍入
110 1111 31/2 1010 000 16 向上舍入
000 0001 1/64 0001 000 1/64 非规格化到规格化

浮点运算

浮点运算结果都是舍入后的结果.

  1. 浮点加法是可交换的: 即 $x +^f y = y +^f x$
  2. 浮点加法不可结合: 例如 (3.14+1e10)-1e10 的值舍入到 0, 而 3.14+(1e10-1e10)的值为 3.14
  3. 浮点加法满足单调性: 即若 $a \geq b$, 则对于任何 $a, b, x$ 的值, 除了 NaN, 都有 $x+a \geq x+b$, 无符号数和补码加法不具有这种属性.
  4. 浮点乘法也是可交换的, 即 $x \times y = y \times x$
  5. 浮点乘法也不可结合: 这些都是由于溢出和舍入引起的.
  6. 浮点乘法在加法上也不具备分配性, 即 1e20(1e20-1e20) 为 0.0, 而 1e201e20-1e20*1e20 为 NaN
  7. 浮点乘法也满足单调性: 对于任何 $a, b, c$ 都不等于 NaN 时, $a \geq b 且 c \geq 0 时,有 a \times c \geq b \times c$; $a \geq b 且 c \leq 0 时,有 a \times c \leq b \times c$; 此外,当 $a \neq NaN$ 时, 有 $a \times a \geq 0$, 无符号数和补码都没有这些单调性

C 中的浮点数

C 标准未规定使用 IEEE 浮点格式, 因此在 C 中无法改变舍入方式, 或者得到诸如 -0, $+\infty$ , $-\infty$ 或者 NaN 之类的特殊值. 大多数系统通过头文件来定义这些常量, 如 GNU 会在 math.h 中定义程序常量 INFINITY(表示 $+\infty$) 和 NAN(表示 NaN).

练习题 2.53 p123

完成下列宏定义, 生成双精度值 $+\infty$, $-\infty$ 和 0:

已知双精度能够表示的最大的有限数大约是 1.8e308, 则下面的代码似乎可以在多种机器上工作, 假设值 1e400 溢出为无穷, 则:

#define POS_INFINITY 1e400
#define NEG_INFINITY (-POS_INFINITY)
#define NEG_ZERO (-1.0/POS_INFINITY)

当在 int, float, double 格式间进行强制类型转换时,程序改变数值和位模式的原则如下(设 int 32 位):

  • 从 int 转换到 float: 不溢出, 可能舍入
  • int 或 float 转换到 double: 能精确转换
  • double 到 float: 可能溢出,可能舍入
  • float 或 double 到 int, 值将会向 0 舍入, 例如 1.99 转换为 1, -1.99 转换为 -1, 也可能溢出. 溢出时(即找不到一个合理的整数近似值), C 标准没有指定固定的结果, 但 Intel 兼容机指定位模式 $[10\cdots 00]$ (即 $TMin_w$) 作为整数不确定值(integer indefinite). 因此, 表达式 (int)+1e10 会得到 -21483648, 即从一个正值变成了一个负值.

练习题 2.54 p123

假设变量 x, f, d 类型分别是 int, float, double, 它们除了不是 $+\infty$, $-\infty$ 和 NaN 外,可以是任意值, 对于下面的每个 C 表达式, 证明它们总是真,或者给出一个使其不为真的值.

float 的最大规格化数是 3.4e38, 小于 1e40, double 的最大规格化数是 1.8e308, 小于 1e309.

A. x == (int)(double) x : int 转换到 double 时能精确转换, 故总为真

B. x == (int)(float) x: int 到 float, 不溢出, 可能舍入, 根据练习题 2.49, 最小不能表示的正数是 $2^{23}+1$, 此时会舍入到$2^{23}$

C. d == (double)(float) d: double 转换到 float 会溢出,也会舍入, d = 1e40 时右边会是正无穷

D. f == (float)(double)f: 都为真

E. f == -(-f): 都为真, 因为浮点数的正负转换只需转换符号位即可.

F. 1.0/2 == 1/2.0: 都为真, 因为分子和分母在运算前都会先转换成浮点数.

G. d*d >= 0.0:实数乘法符合单调性, 故都为真, 不过可能溢出为 $+\infty$

H. (f+d)-f == d: 当 f+d 溢出时, 不为真, 例如 f=1e20, d=1.0 时, 左边会 0,右边 为 1.0

参考

]]>