<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Sharpmark &#124; 刘炯的博客 &#187; 编程卓越之道</title>
	<atom:link href="http://liujiong.com/blog/tag/%e7%bc%96%e7%a8%8b%e5%8d%93%e8%b6%8a%e4%b9%8b%e9%81%93/feed/" rel="self" type="application/rss+xml" />
	<link>http://liujiong.com</link>
	<description>Version 3.0</description>
	<lastBuildDate>Sat, 28 Jan 2012 08:28:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>数据对齐</title>
		<link>http://liujiong.com/blog/posts/align-data/</link>
		<comments>http://liujiong.com/blog/posts/align-data/#comments</comments>
		<pubDate>Mon, 12 Feb 2007 07:55:00 +0000</pubDate>
		<dc:creator>sharpmark</dc:creator>
				<category><![CDATA[文摘笔记]]></category>
		<category><![CDATA[计算机科学]]></category>
		<category><![CDATA[applied-C++]]></category>
		<category><![CDATA[simple]]></category>
		<category><![CDATA[标准C++]]></category>
		<category><![CDATA[编程卓越之道]]></category>
		<category><![CDATA[编程技巧]]></category>

		<guid isPermaLink="false">http://218.240.14.21/~sharpmark/?p=79</guid>
		<description><![CDATA[看《编程卓越之道Write Great Code》的时候，在看数据总线的时候，明白了一些内存访问的基本原理，而最近在做SIMPLE，正好可以利用内存访问的一个技巧来优化一下我的程序。这 个技巧就是&#8221... ]]></description>
			<content:encoded><![CDATA[<p>看《编程卓越之道Write Great Code》的时候，在看数据总线的时候，明白了一些内存访问的基本原理，而最近在做SIMPLE，正好可以利用内存访问的一个技巧来优化一下我的程序。这 个技巧就是&#8221;数据对齐&#8221;。顺便拿出来分享一下。我会简单说一下数据对齐的由来，原因和应用方法。</p>
<p>首先说数据对其这种说法怎么来的。我以使 用16位(16-bit)数据总线的处理器来举例。32位的原理相同。处理器将16位的数据总线分为两部分d0-d7为低字节，d8-d15为高字节。相 应将内存分为对应两列。偶地址的接在d0-d7数据线上，奇地址的接在d8-d15位上。这样处理器就可以任意访问一个偶地址的数据，或一个奇地址的数 据，或者一个以偶地址开头的字(Word, 1word == 2byte)。<br />
然而，例如在80&#215;86系列的16位处理器上，它允许从任意地址读 取一个字。处理器从指定的地址取得低端字节，而从下一个地址获得高端字节。如果低端地址是偶地址的话，没有什么问题。但是，如果是奇地址的话，那么数据的 高端字节就需要到下一个字去获得了。例如希望从地址21获取一个字。这个数据位置就是在21, 22两个字节。但是要注意，处理只能将偶地址作为低位放入地址总线，所以处理器首先通过访问地址为20, 21的字节，获得一个字，然后再访问下一个字，获得地址为22, 23的两个字节。并在内部将地址21和地址22的数据交换位置，通过数据总线传递给处理器。<br />
这在大规模访问连续数据的时候就会产生问题(当然其他 时候也可能会有)。例如，一个图像数据顺序保存在内存中，假设每个象素是两字节，数据的起始字节是奇数。并假设处理器没有做任何优化，没有缓存，那么每个 象素的读取都会有两次的内存访问，一次的高低字节数据交换。一个800*600的图像就会有480000次的多余内存访问。这个效率会差很多。所以要想办 法解决。</p>
<p>这里我介绍两种需要手动设置数据的对齐的情况。<br />
1. 在一个大的数据块的开始，如果数据没有对齐，那么后面的数据都回相应错位。设置数据开始部分对齐的方法如下：(从SIMPLE中摘录，为了便于阅读，有改 动，另由于blogger对尖括号的支持不够好，所以下面的代码中可能使用全角的尖括号代替半角尖括号，使用时请自行替换。)</p>
<p>[code:c]&lt; T* &gt; alignPointer(void * raw, int align)<br />
{<br />
T*p = reinterpret_cast&lt;T*&gt;(<br />
(reinterpret_cast&lt;uintptr_t&gt;(raw) + align - 1)<br />
&amp;~(align - 1);<br />
return p;<br />
}[/code]</p>
<p>解释一下，假设我们需要N个字节的数据，并以align对齐。(align==0,1的时候不对齐, 2的时候两字节对齐，以此类推，align为2的幂。)。void * raw是申请的空间，大小为N + align &#8211; 1个空间。多余的align &#8211; 1个空间是为了对齐而预留的。T类型是指定的每个元素的类型。</p>
<p>首先，通过reinterpret_cast&lt;uintptr_t&gt;(raw)将raw转化成uintptr_t类型，(uintptr_t是一种足够大从而可以保存指针的类型，如果没有定义uintptr_t的系统，可以使用typedef int uintptr_t来自行定义)。<br />
结果加align &#8211; 1，相当于这个元素最后一个字节的位置。然后对~(align &#8211; 1)取与(and)运算。相当与计算出元素最后一个字节所在的内存的对齐列的第一个字节的地址。这么说有些晕，我来举个例子：例如分配n个字节，其中起始地址是7，对齐字节是4字节对齐。如果不对齐的话那么这个数据的第一个元素占用的就是7~10字节。如果对齐的话，首先7 + (4 &#8211; 1) = 10，计算出理论的最后一个字节的地址是10。然后10 &amp; ~3。换算成二进制更好理解：1011 &amp; 1100 = 1000，也就是十进制的8。所以起始的字节是从8开始的，那么地址是7的字节以及分配的最后两个字节会被忽略。再假设起始地址是4，那么计算后还是4，不用对齐。<br />
2. 行对齐。在图像的内存表示中，除了开头的内存对其外，还有一个地方需要注意，就是行与行之间的对齐。例如一个RGB像素3字节，一个2*3的图像，再内存中的布局应该是：</p>
<p><code>0- 2为(0, 0) 3- 5为(1, 0) 6- 7空闲<br />
8-10为(0, 1) 11-13为(1, 1) 14-15空闲<br />
16-18为(0, 2) 19-21为(1, 2) 22-23空闲</code></p>
<p>图像的每一行都需要8个字节存储。不过其中仅有6个字节包含象素数据。前三个字节保存该行第一个象素的存储，其后3个字节保存下一个像素。为了另下一行从双字开始，在存储下一行的象素之前必须跳过两个字节。这里的做法跟上面的那种差不多，所以就不再举出代码了。</p>
]]></content:encoded>
			<wfw:commentRss>http://liujiong.com/blog/posts/align-data/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>浮点表示法</title>
		<link>http://liujiong.com/blog/posts/floating-point-repesentation/</link>
		<comments>http://liujiong.com/blog/posts/floating-point-repesentation/#comments</comments>
		<pubDate>Wed, 10 Jan 2007 07:05:00 +0000</pubDate>
		<dc:creator>sharpmark</dc:creator>
				<category><![CDATA[文摘笔记]]></category>
		<category><![CDATA[计算机科学]]></category>
		<category><![CDATA[编程卓越之道]]></category>

		<guid isPermaLink="false">http://218.240.14.21/~sharpmark/?p=62</guid>
		<description><![CDATA[“为了表示实数，大多数浮点格式使用一些位来表示尾数(mantissa)，一小部分位来表示阶码(exponent，又译作指数)。尾数是一个基数，它的值通常落在一个有限的范围内（例如，零到一），而阶码... ]]></description>
			<content:encoded><![CDATA[<p>“为了表示实数，大多数浮点格式使用一些位来表示尾数(mantissa)，一小部分位来表示阶码(exponent，又译作指数)。尾数是一个基数，它的值通常落在一个有限的范围内（例如，零到一），而阶码是一个乘数，他作用到位数值后，产生的值就超出了这个范围。将浮点数分成这两部分结果就是，浮点 数只能以一定数量的有效数字(significant digit)来表示数值。”</p>
<p>“为了提高浮点计算的准确性，有必要在计算过程中使用额外的有效数字。这些额外的有效数字被称为保护位（guard digits，二进制格式时为guard bits）。在进行一长串的运算时，保护为极大地提高了准确性。”</p>
<p><span id="more-62"></span></p>
<p><span style="font-weight: bold">有关有限精度计算的几个重要规则</span><br />
“计算的顺序可以影响结果的准确性。将运算分组，其原则是先对数量级接近的数进行相加或者相减，再对数量级相差较大的数进行运算。<br />
在对符号相同的两个数做减法或者符号不同的两个数做加法时，结果的精度可能比所用的浮点格式所能支持的精度要小。<br />
在进行一系列涉及到加法、减法、乘法与处罚的计算时，尽量先作乘法与除法。<br />
在对一组数作乘法或除法时，尽量对数量级相对一样的数作乘法与除法。<br />
在比较两个浮点数是否相等时，要做的是计算这两个数的差，看它是否小于某个很小的误差值。例如<code><span style="color: #0000ff">if</span>( (value1 - value2) // then ...</code>”</p>
<p><span style="font-weight: bold">IEEE浮点数格式</span><br />
<code><span style="color: #008000"></span></code>IEEE有三种浮点数格式：单精度(single precision)，双精度(double precision)，以及扩展精度(extended precision)。这里举单精度为例。<br />
“单精度浮点格式使用24位的尾数与8位的阶码。尾数通常表示的值在大于等于1.0到小于2.0之间。尾数的最高位总是假定为一，正好是在二进制小数点左边的第一个位，而余下的23个尾数位则在小数点的右边。”<br />
“尾数使用一的补码格式而不是二的补码。这意味着尾数的24位值通常是一个无符号数，而位于第31位的符号为决定了正负。”而第23~30位表示阶码，0~22表示尾数。(其中有23个尾数的位，而最高位是1，所以有了24位。)<br />
“为了表示1.0到小于2.0范围之外的数，就需要阶码了。浮点格式将计算二的有阶码的值指定的幂，并将尾数乘以这个数。阶码是八位的，它使用余- 127格式(excess-127)，有时候也成为移-127(bias-127)阶码。使用余-127格式，阶码2^0表示为127(0x7F)。因 此，为了将一个阶码转换为余-127格式，只需要加上127就可以了。阶码使用余-127格式简化了浮点数的比较。因为如果我们将符号位（第31位）单独 处理，那么对两个浮点数作小于或者大于的比较就很容易了，只需要将它们看作是无符号整型数作比较就可以了。”<br />
“将尾数左移一位同时递减阶码就不会改变浮点数的值。”反之亦然。</p>
<p><span style="font-weight: bold">规格化(normalization)与反向规格化(denormalized)数</span><br />
“规格化浮点数就是尾数最高位是一的浮点数。”<br />
“在两种重要的情况下，浮点数是无法被规格化的，零就是特例之一。浮点格式支持+0也支持-0（取决于符号位的值）。Intel推荐使用符号位来表示该零值是负数下溢产生的（符号位置一）还是正数下溢产生的（符号位清零）。”<br />
“无法规格化浮点数的第二种情况是尾数的高端几个尾是零，但是移位阶码（移位意味着给数值加上偏移量，如余-127格式的移位是127）也是零。IEEE允许使用特殊的反向规格化数来表示它们。”</p>
<p><span style="font-weight: bold">舍入(rounding)</span><br />
舍位(truncation)：直接舍弃额外的精度。<br />
上舍入(rounding up)：将结果置为目标浮点格式中大于当前之中所有数的最小值。对应函数ceil。<br />
下舍入(rounding donw)：将结果置为目标浮点格式中小于当前之中所有数的最大值。对应函数floor“舍位与下舍入之间存在着微妙的差别。舍位总是是的结果更接近零。 对于正数来说，设位于下舍入的结果是一样的。但是，对于负数，舍位只是简单地使用尾数中现有的位，而下舍入会给最低位加上一。”<br />
四舍五入 (rounding to nearest)：“如果保护位的值小于尾数最低位的值的一半，那么四舍五入方式会将结果舍入到小值中的最大值（忽略符号位）。如果保护位的值小于尾数最 低位的值的一半，那么四舍五入方式会将结果舍入到大值中的最小值（忽略符号位）。如果保护位的值正好等于尾数最低位的值的一半，IEEE浮点标准的说法是 上舍入和下舍入各半。具体实现可以是将尾数舍入到最低位等于零。也就是说，如果当前的尾数的最低位已经是零，就直接使用该尾数值，如果当前的尾数值最低位 等于一，就将尾数加一。”</p>
<p><span style="font-weight: bold">特殊的浮点数</span><br />
“如果阶码全一儿尾数非零（不管隐含位），那么尾数的最高位（仍然不包括隐含位）用来表示这个数是一个未明无效数字(quiet not-a-number, QNaN)或者明确无效数字(signaling not-a-number, SNaN)，这些无效数字(not-a-number, NaN)结果告诉系统计算过程中出现了严重的错误，计算的结果是完全不确定的。未明无效数字代表不确定的结果，而明确无效数字则代表确定发生了无效的操 作。任何运算中，只要存在无效数字，结果就是无效数字。”<br />
“另两种特殊的浮点数表示是阶码全一，而且尾数全零。这种情况下，符号位表示该书是正无穷大(+infinity)或者是负无穷大。”</p>
]]></content:encoded>
			<wfw:commentRss>http://liujiong.com/blog/posts/floating-point-repesentation/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>ASCII特性</title>
		<link>http://liujiong.com/blog/posts/ascii/</link>
		<comments>http://liujiong.com/blog/posts/ascii/#comments</comments>
		<pubDate>Tue, 09 Jan 2007 07:02:00 +0000</pubDate>
		<dc:creator>sharpmark</dc:creator>
				<category><![CDATA[文摘笔记]]></category>
		<category><![CDATA[计算机科学]]></category>
		<category><![CDATA[编程卓越之道]]></category>

		<guid isPermaLink="false">http://218.240.14.21/~sharpmark/?p=61</guid>
		<description><![CDATA[“ASCII（美国信息交换标准码， American Standard Code for Information Interchange） 字符集将128个字符映射到无符号整型数0 .. 127 (#0 .. #7F)。” “ASCII字符集被划分为四个包含32个字符的组。头32个字符，... ]]></description>
			<content:encoded><![CDATA[<p>“ASCII（美国信息交换标准码， American Standard Code for Information Interchange） 字符集将128个字符映射到无符号整型数0 .. 127 (#0 .. #7F)。”<br />
“ASCII字符集被划分为四个包含32个字符的组。头32个字符，其ASCII码为#0到#1F(0到31)，组成了一组特殊的非打印字符，被称为控制字符。”<br />
“第二组32个ASCII字符码包括各种标点符号，特殊字符以及数字。驻足字符中最值得关注的包括空格字符（ASCII码#20）以及数字（ASCII码#30..#39）。”<br />
“第三组32个ASCII字符码包括大写字符字符。字符A到Z的ASCII码分布在#41..#5A的范围内。由于只有26个字母字符，多出来的六个码表示一些特殊符号。”<br />
“第四组也是最后一组32个ASCII字符码表示小写字母字符，五个特殊符号以及另一个控制符（删除delete）。”</p>
<p>“第五位与第六位决定了字符所在的组。”五六位为00，表示控制字符，01表示数字与标点符号，10表示大写字母与特殊字符，11表示小写字符与特殊符号。</p>
<p>“如果你将大写字母与小写字母的编码转换成二进制，你会注意到大写字母与对应的小写字母的符号差别只有一位。”例如，E的二进制编码是 01000101，e的编码是01100101。相差第五位。“这两个ASCII码的唯一不同之处在第五位。大写字母符号的第五位永远是零，而小写字母字 符的第五位永远是一。你可以利用这个事实来快速地将一个字母字符在大写与小写之间进行转化，只须将第五位取反即可。”</p>
<p>数字的十六进制“ASCII码的低端半字节等于被表示的数。只要将ASCII码的高端半字节去掉（置全零），就得到了该数字的二进制表示。”反之可得到对应数字的ASCII码。</p>
]]></content:encoded>
			<wfw:commentRss>http://liujiong.com/blog/posts/ascii/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

