各フィールドが消費している値は、フィールドの先頭アドレスと構造体全体の先頭アドレスの差である. アドレス計算(比較)は同じ型のポインタ同士でなければならない.異なる型の2つのポインタの間で比較を行うには、両方とも(void*)にキャストする必要がある.
ポインタが32ビットの環境で実行した結果を示す.各フィールドの領域合計の総和と、構造体全体の占める領域の差(この環境では12バイト)が詰め領域の大きさである.
off(firstname)=0 off(lastname )=20 off(score[0] )=40 off(score[1] )=44 off(ratio )=48 total =64
なお最新の環境(C99)にはstddef.hというヘッダがあり、ここにoffsetofマクロが定義されているので、これを使用するとベースアドレスとの間の減算が不要になる.
strsize2.coffsetofが定義されていない環境では以下のように定義すればほぼ同様の結果を得ることができる.
#define offsetof(type, field) ((size_t)&(((type*)0)->field))
注意:
ポインタがintより大きい環境(ほとんどの64bit OS)では、ポインタをintにキャストすると正しく動作しない可能性があるので注意. 32bitOSでは動作しても、64bitOSで動作しないプログラムはintとポインタの扱いに誤りがある場合が多い.
また構造体のメンバ以外では、変数のアドレスの順序は変数の宣言順序とは必ずしも一致しない.
例えばint p; int q; と宣言しても、&p < &qとなる保障はない.特に最適化オプションをつけてコンパイルした場合は、コンパイラが変数を並べ替えることがあるので要注意である.
aが配列の場合に限り、i<jならば&a[i] < &a[j]が保障されている.
ポインタの示す先に領域が確保されず、初期値が0のままなので、 内容をアクセスした途端に異常終了する.
構造体のデータ構造定義は本文と同じである.
関数makelistは入力された文字列配列(list)から
構造体を要素とする線形リストを作成する.
mはリストの先頭要素へのポインタである.
ただし最初はダミー要素を接続する(31行目).
関数lengthはダミーを除いた要素の個数を数えて返す.
なお文字列を姓と名に分離する処理には、
標準入力の代わりに文字列から読み込んで変換するsscanfが使用できるが、
scanfと同様に不正な入力に対しては正しく動作しない.
これを回避するため、ここでは
等価な関数separateを作成して使用している(36行目).
add_fraction(a,b,c)のうち、cは値を変更するので値渡しにすることができない.
a,bは値渡し、参照渡しのどちらも可能だが、
プログラムの統一性と実行効率を考えると参照渡しの方がよい.
なお値を変更しない参照渡しの引数にはconst宣言を付加するのが望ましい(=>7章).
Fractionは分子(num),分母(dom)を持つ構造体として定義する. 9-13行目はプロトタイプ宣言である. 解答4.4で示したとおり、すべて参照渡しで 値を変更する必要のないものはconst宣言を付加するのが望ましい. gcdは最大公約数をEuclidの互助法で求める関数、set_fractionは 分数を作成、copy_fractionはコピーする関数である. add_fractionは分数の加算を行うが、 最初にgcdを使用して分母の最大公約数dを求めておき、 通分の際にdであらかじめ割っておくことにより オーバフローを極力防いでいる. 加算結果は約分が必要であるが、これはset_fractionで行うことにする.
test_fractionは単体テストである. まず3/42は約分され1/14になる. これに22/35を加算すると gcd(14,35)=7なのでx=1*35/7+22*14/7=49, y=14*35/7=70、 これを約分するので結果は7/10である. すなわち1/14 + 22/35 = 7/10と出力されれば通分、加算、約分は全て 正常に動作していることがわかる. 実際は値を変えていくつかのテストを行う必要がある(=>5章).
S1からS15までを求めた結果を以下に示す.
1: 1/ 1=1.00000000 2: 5/ 4=1.25000000 3: 49/ 36=1.36111111 4: 205/ 144=1.42361111 5: 5269/ 3600=1.46361111 6: 5369/ 3600=1.49138889 7: 266681/ 176400=1.51179705 8: 1077749/ 705600=1.52742205 9: 9778141/ 6350400=1.53976773 10: 1968329/ 1270080=1.54976773 11: 239437889/ 153679680=1.55803219 12: 1895814/ 4548871=0.41676583 13: 324941437/ 768759199=0.42268299 14: 32771411/ 352947644=0.09285063 15: 0/ 1=0.00000000
S12以降は結果が間違っている. これは途中でオーバフローが発生しているためである.
ちなみに分子と分母をともに64ビット符号なし整数に変更したのがfrac2.cである. #defineを使用して32ビット(uint32_t)/64ビット(uint64_t)を変更できるようにしてある. 56-57行目は出力の切り替えである. この例では手作業で行っているが、これもPRIu32,PRIu64などを使用して #ifdefにより自動的に行うこともできる.
ROUNDを定義すると精度を落としてオーバフローを回避する計算を行う.
double r0,r1はオーバフローの検出用である. Snは単調増加するはずなので、
Sn+1がSnより小さければオーバフロー発生と判断する.
n=1から20の実行結果を示す. 今度は正しく計算できている.
n=18で分子、分母とも値が小さくなっているが、これは
オーバフロー回避のため両方とも定数で
割っているためである(端数は四捨五入).
丸めた後も結果の小数点以下10桁めまでは変化しないので実用上問題はない.
1: 1/ 1=1.0000000000 2: 5/ 4=1.2500000000 3: 49/ 36=1.3611111111 4: 205/ 144=1.4236111111 5: 5269/ 3600=1.4636111111 6: 5369/ 3600=1.4913888889 7: 266681/ 176400=1.5117970522 8: 1077749/ 705600=1.5274220522 9: 9778141/ 6350400=1.5397677312 10: 1968329/ 1270080=1.5497677312 11: 239437889/ 153679680=1.5580321940 12: 240505109/ 153679680=1.5649766384 13: 40799043101/ 25971865920=1.5708937982 14: 40931552621/ 25971865920=1.5759958390 15: 205234915681/ 129859329600=1.5804402834 16: 822968714749/ 519437318400=1.5843465334 17: 238357395880861/ 150117385017600=1.5878067411 18: 38688956825231/ 24319016372916=1.5908931608 19: 139910324302701/ 87791649106169=1.5936632439 20: 280259606850931/ 175583298212400=1.5961632439
以下n=100まで10おきに示す.
30: 232620060796043/ 144291811448700=1.6121501176 40: 11221770783643/ 6925975988800=1.6202439630 50: 1180535656195419/ 726424144797500=1.6251327336 60: 384540276284609/ 236145279632400=1.6284055175 70: 639458579458353/ 392125473395950=1.6307499075 80: 2137311518593/ 1309216528640=1.6325118663 90: 4801177109740001/ 2938504674293100=1.6338844555 100: 987357358228903/ 603894239030000=1.6349839002
これ以降は1.64を越えるのはn=203, 1.644を越えるのはn=1071である.
n=3400を少し越えたところでオーバフローにより
エラーが検出されたが、
その直前でもまだ小数点以下4桁目が収束していなかった.
3427: 162014319957278146/ 98510368543594591=1.6446423087 3428: 147964438981294563/ 2894033666659139056=0.0511274076 error
なお配列を使用すれば原理的には無限桁数の計算が可能である. この応用として有名なのはπの計算(ただし10万桁程度まで)に 使用される以下の式である
tan-1(1/x) = 1/x-1/3x3+1/5x5+...+(-1)i/(2i+1)x(2i+1)+... π = 16*tan-1(1/5) - 4*tan-1(1/239) = 16*[1/5−1/(3*53)+1/(5*55)-...] - 4*[1/239-1/(3*2393)+1/(5*2395)-...]
データ定義とアルゴリズムの一部を示す. 興味のある方は計算してみるとよいだろう.
typedef struct bigint { int cnt; // vecの要素数 int *vec; // 本体(8桁ずつ) int size; // 使用している最大位置 // (10E8未満なら0,10E16未満なら1,...) } Bigint; typedef struct fraction { Bigint num; // 分子 Bigint dom; // 分母 } Fraction; Fraction fx,fy,fz,tmp1,tmp2; // !! 要領域確保 !! void calc() { int c = 1000; /* 項数 */ set_fraction(1,5,&fx); // fx=1/5 atan(&fx,c,&tmp1); // tmp1=atan(1/5) mul(&tmp1,16,&fx); // fx=16*atan(1/5) set_fraction(1,239,&fy); // fy=1/239 atan(&fy,c,&tmp2); // tmp2=atan(1/239) mul(&tmp2,4,&fy); // fy=4*atan(1/239) sub(&fx,&fy,&fz); // fz=pi print_fraction(&fz); }