1 BAB 4 PERANCANGAN DAN IMPLEMENTASI Pada bab ini akan dibahas 3 hal secara garis besar, yaitu perancangan, implementasi, dan evaluasi. Pada bagian pe...
Pada bab ini akan dibahas 3 hal secara garis besar, yaitu perancangan, implementasi, dan evaluasi. Pada bagian perancangan akan dibahas mengenai rancangan dari grammar dan sintaksis untuk operator Division yang diusulkan, lalu pada bagian implementasi akan dibahas proses kodifikasi dari rancangan algoritma yang telah dirancang pada bab sebelumnya, kemudian pada bagian evaluasi akan membahas proses dan hasil pengujian dari operator Division yang telah diimplementasikan. 4.1 Perancangan Pada bagian ini akan dibahas perancangan grammar dan sintaksis dari algoritma yang telah dirancang pada bab sebelumnya. Namun sebelum itu, akan dijelaskan beberapa hal kententuan mengenai perancangan itu sendiri: •
Operator Division akan diimplementasikan dengan pola sintaksis yang mirip seperti operator Join secara gramatikal. Karena sebagaimana operasi Join, operasi Division melibatkan lebih dari satu tabel, dan memerlukan kolom yang dijadikan sebagai syarat (kolom faktor pembagi).
•
Algoritma yang diimplementasikan adalah Algoritma 1 untuk Definite Division dan Algoritma 2 untuk Partial Division. Alasan dari pemilihan Algoritma 2 daripada Algoritma 3 dikarenakan salah satu langkah pada Algoritma 3 melakukan operasi Definite Division yang DIVISOR-nya merupakan inner set yang belum didukung pada SQL. Hal ini membuat
84
85 Algoritma 2 lebih realistis untuk diimplementasikan dibandingkan dengan Algoritma 3. •
Algoritma 1 dan Algoritma 2 diimplementasikan dengan menggunakan operator yang sama secara sintaksis, yaitu DIVIDE.
•
Pembedaan pengenalan jenis operasi yang dilakukan, baik itu jenis Definite Division maupun jenis Partial Division akan dilakukan pada kode-kode yang memproses sintaks DIVIDE tersebut.
4.1.1 Rancangan Standar SQL Rancangan standar SQL yang diusulkan disusun dalam bentuk BNF yang merupakan hasil modifikasi dari BNF grammar standar pada ANSI SQL v2 (SQL-92). Alasan digunakannya versi standar tersebut adalah karena adanya pertimbangan kemudahan portabilitas terhadap beberapa engine RDBMS yang ada, yang di mana umunya secara keseluruhan sudah mendukung standar tersebut. Modifikasi yang dilakukan adalah dengan menyisipkan beberapa tambahan spesifikasi terhadap penggunaan operasi Division. Berikut dilampirkan sebagian dari spesifikasi tersebut khususnya pada bagian untuk melakukan QUERY (SELECT STATEMENT) : Gambar 4.1 Grammar BNF SELECT STATEMENT yang Diusulkan … <scalar subquery> ::= <subquery> <subquery> ::=
expression>
[ <join specification> ] <join type> ::= INNER | [ OUTER ] | UNION ::= LEFT | RIGHT | FULL <join specification> ::= <join condition> | <join condition> ::= ON <search condition> ::= USING <join column list> <join column list> ::=
table>
::=
reference>
DIVIDE
reference>
paren>
name
specification>
specification>
::=
USING
list>
paren> <where clause> ::= WHERE <search condition> ::= GROUP BY ::= [ { }... ] ::= [ ] ::= COLLATE ::= ::= HAVING <search condition>
::= VALUES
::= [ { }... ] <explicit table> ::= TABLE
::= <non-join query term> | <joined table> |
87
spec>
::=
CORRESPONDING
[
BY
paren>
column list> ] ::=
::= <non-join query primary> | <joined table> |
Untuk daftar spesifikasi BNF grammar selengkapnya disertakan pada bagian lampiran. Berikut akan dilampirkan diagram yang merupakan diagram alur hasil representasi dari sebagian BNF grammar tersebut : Gambar 4.2 BNF yang diusulkan
88
89
90
Contoh Query : 1. Contoh Benar #1 SELECT * FROM SupplierPart DIVIDE ( SELECT PartID FROM Part WHERE PartLength = 20 ) AS P1 USING (PartID) Gambar 4.3 Contoh 1 Query Benar
Contoh di atas merupakan contoh query yang benar secara sintaksis dan akan melakukan operasi Division antara tabel ‘SupplierPart’ dengan sebuah subquery alias ‘P1’. 2. Contoh Benar #2 Query yang ada pada Gambar 4.4 juga dianggap benar secara sintaksis meskipun terdapat beberapa subquery dan operasi Join yang bertumpuk (cascading) dan operasi Division dengan faktor pembagi lebih dari satu kolom.
91 SELECT SupplierName FROM Supplier S JOIN SupplierParts SP ON
S.SupplierID
=
SP.SupplierID
JOIN
PartColor
PC
ON
SP.PartID = PC.PartID DIVIDE ( SELECT
*
FROM
Part
JOIN
PartColor
ON
Part.PartID
=
PartColor.PartID WHERE ColorID IN ( SELECT ColorID FROM Color WHERE ColorName = ‘Red’ ) ) AS PC1 USING (PartID,ColorID) Gambar 4.4 Contoh 2 Query Benar
3. Contoh Benar #3 SELECT * FROM SupplierPart DIVIDE ProductPart USING (PartID) Gambar 4.5 Contoh 3 Query Benar
Query di atas merupakan salah satu contoh penggunaan operasi Partial Division. 4. Contoh Salah #1 SELECT * FROM
SupplierPart DIVIDE PartColor Gambar 4.6 Contoh 1 Query Salah
Query di atas salah secara sintaksis karena klausa operasi Division membutuhkan
kondisi
pembagian
yang
diinginkan
(memerlukan
“USING”) . 5. Contoh Salah #2 SELECT
*
FROM
SupplierPart
SP
DIVIDE
PartColor
PC
BY
SP.PartID = PC.PartID Gambar 4.7 Contoh 2 Query Salah
Query di atas juga salah secara sintaksis karena menggunakan kata kunci “BY” yang seharusnya menggunakan kata kunci “USING”.
92 6. Contoh Salah #3 SELECT * FROM SupplierPart DIVIDE ProductPart USING PartID Gambar 4.8 Contoh 3 Query Salah
Query di atas juga salah secara sintaksis karena tidak menggunakan simbol kurung buka ‘(‘ dan kurung tutup ‘)’ untuk membatasi kolom yang digunakan sebagai faktor pembaginya. 4.2 Implementasi Sebelum masuk kepada pembahasan kodifikasi dari operator Division, akan dijelaskan beberapa ketentuan mengenai implementasi yang dilakukan: •
Implementasi dilakukan pada salah satu engine RDBMS yaitu SQLite (http://www.sqlite.org).
SQLite
adalah
sebuah
embedded-engine
RDBMS dalam bentuk in-process library yang dikembangkan dengan menggunakan bahasa pemograman C. Alasan utama dari pemilihan SQLite adalah karena SQLite merupakan engine RDBMS yang sifatnya public domain dan open source, sehingga lebih realistis untuk diterapkan sebagai objek implementasi dari algoritma yang telah dirancang. Alasan lainnya dari pemilihan SQLite adalah karena SQLite merupakan engine RDBMS yang saat ini telah banyak digunakan di seluruh dunia dan telah diterapkan ke beberapa proyek-proyek software aplikasi skala besar seperti : Adobe Photosop Lightroom, Adobe Acrobat Reader, Apple iPhone OS, Apple Safari, Mozilla Firefox, Google Android OS, PHP, Phyton, Symbian OS, dll. Beberapa vendor-vendor besar yang tercatat telah menggunakan SQLite antara lain: Adobe,
Implementasi yang dilakukan adalah dengan memodifikasi engine dalam hal ini library SQLite agar dapat mengenal token “DIVIDE” dengan syntax yang sesuai secara grammar yang dirancang sebelumnya dan juga menghasilkan output yang sesuai dengan operasi Division.
4.2.1 Tahapan Proses Build SQLite SQLite diimplementasikan dengan menggunakan bahasa ANSI-C yang terdiri atas beberapa file, namun banyak di antara kode-kode C tersebut yang merupakan hasil generasi dari script ataupun dari program tertentu (autogenerated file). Diagram berikut ini merupakan tahapan proses dari pembangunan SQLite :
Gambar 4.9 Digram Proses Pembentukan SQLite
94 Pada gambar di atas, oval merah merupakan file sumber yang asli, oval hijau adalah kode C yang digenerasikan secara otomatis. Kotak biru adalah compiler dan tools yang dibutuhkan sebagai dasar untuk mengkompilasi SQLite, dan kotak kuning adalah tools dan compiler yang dimana kode nya sudah merupakan bagian dari source code SQLite. Dan final output-nya merupakan oval ungu yaitu sebagai library dari SQLite. “Other source files” merupakan kumpulan file kode C sebanyak 107 files (termasuk header-files, untuk daftar nama selengkapnya dapat dilihat di lampiran) yang merupakan penunjang inti dari SQLite yang dimana salah satunya adalah “SELECT.C” sebagai inti utama dari proses yang dilakukan pada keseluruhan statement SELECT. Berdasarkan analisa atas keseluruhan alur dari proses yang ada, maka langkah-langkah modifikasi yang dilakukan adalah : 1. Menambahkan kata kunci DIVIDE agar dapat dikenali oleh parser. 2. Memodifikasi grammar pada parser agar dapat mengenali pola syntax DIVIDE . 3. Menambahkan tipe jenis baru selain JOIN dan memodifikasi parser untuk menggenerasikan kode untuk jenis tersebut. 4. Menambahkan fungsi proses baru sebagai inti dari rutin-rutin pemrosesan yang dilakukan terhadap jenis DIVIDE . 4.2.2
Menambah Kata Kunci Baru Seluruh kata kunci / token yang ada pada SQLite terdapat pada file “mkkeywordhash.c” yang nantinya akan dikompilasi dan menghasilkan
95 output file “keywordhash.h” sebagai sebuah auto-generated file yang digunakan sebagai sumber kata kunci pada library SQLite. Gambar 4.10 Modifikasi File "mkkeywordhash.c" mkkeywordhash.c … { "USING", "TK_USING", ALWAYS { "VACUUM", "TK_VACUUM", VACUUM { "VALUES", "TK_VALUES", ALWAYS { "VIEW", "TK_VIEW", VIEW { "VIRTUAL", "TK_VIRTUAL", VTAB { "WHEN", "TK_WHEN", ALWAYS { "WHERE", "TK_WHERE", ALWAYS }; …
Modifikasi Grammar Parser pada SQLite menggunakan Lemon sebagai parser generator yang akan menggenerasikan kode-kode yang akan dieksekusi berdasarkan input grammar yang ada pada file “parse.y”. Dengan menggunakan template yang ada pada file “lempar.c” ditambah dengan file “parse.y” hasil modifikasi, maka akan menghasilkan output file “parse.c” & “parse.h” yang merupakan kode parser yang akan digunakan pada library SQLite. Gambar 4.11 Modifikasi file "parse.y" parse.y … stl_prefix(A) ::= seltablist(X) joinop(Y). {A = X;
Menambah Tipe Jenis Flag Baru Setelah parser menggenerasikan kode yang akan memberikan flag kepada jenis operasi baru tersebut, maka harus ditambahkan flag baru sebagai preprocessor code agar keseluruhan library dapat mengenal jenis operasi baru tersebut Gambar 4.12 Modifikasi File “sqliteint.h” sqliteint.h … #define JT_INNER 0x0001 /* Any kind of inner or cross join */ #define JT_CROSS 0x0002 /* Explicit use of the CROSS keyword */ #define JT_NATURAL 0x0004 /* True for a "natural" join */ #define JT_LEFT 0x0008 /* Left outer join */ #define JT_RIGHT 0x0010 /* Right outer join */ #define JT_OUTER 0x0020 /* The "OUTER" keyword is present */ #define JT_ERROR 0x0040 /* unknown or unsupported join type */ …
sqliteint.h … #define JT_INNER 0x0001 /* Any kind of inner or cross join */ #define JT_CROSS 0x0002 /* Explicit use of the CROSS keyword */ #define JT_NATURAL 0x0004 /* True for a "natural" join */ #define JT_LEFT 0x0008 /* Left outer join */ #define JT_RIGHT 0x0010 /* Right outer join */ #define JT_OUTER 0x0020 /* The "OUTER" keyword is present */ #define JT_ERROR 0x0040 /* unknown or unsupported join type */ #define JT_DIVIDE 0x0030 /* DIVIDE operator flag type */ …
98 4.2.5
Menambahkan Proses DIVIDE Langkah terakhir yaitu dengan mengubah alur pada rutin pengaplikasian statement SELECT untuk membedakan antara jenis operasi JOIN dan operasi DIVIDE dan menambahkan fungsi baru processDivide() pada file
“Select.C” sebagai inti utama dari rutin-rutin yang akan dilakukan untuk memproses operasi Division tersebut sesuai dengan algoritma yang telah dirancang sebelumnya. Untuk kode selengkapnya dari rutin fungsi processDivide() disertakan pada lampiran. Gambar 4.13 Penambahan Pemanggilan rutin Proses DIVIDE select.c … /* Process NATURAL keywords, and ON and USING clauses of joins. */ if( db->mallocFailed || sqliteProcessJoin(pParse, p) ){ return WRC_Abort; }…
99 for(j=0; jnId; j++){ char *zNameUsing = pList->a[j].zName; if(sqlite3StrICmp(zName, zNameUsing)==0){ found++; break; } } if(found){ continue; } } } } } … if( (pLeft[1].jointype & JT_DIVIDE)!=0 && columnIndex(pLeft>pTab, zName)>=0 ){ /* In a DIVIDE clause omit the division factor columns from the table on the right */ continue; } …
100 4.3 Evaluasi Untuk melakukan pengujian lebih lanjut, maka kumpulan kode-kode library SQLite sebanyak 107 files yang ada sebelumnya akan dikompilasi bersama-sama dengan file kode ‘shell.c’ sebagai inti dari aplikasi siap pakai (executable) yang disediakan oleh pengembang SQLite dan berfungsi sebagai shell console untuk melakukan setiap fungsi operasi terhadap engine SQLite yang ada seperti penulisan query, hingga ke pengoperasian fisikal terhadap database seperti pembuatan database-file baru dan lain-lain. Sebagai sebuah catatan tambahan bahwa tidak dilakukan perubahan atau modifikasi apapun terhadap kode-kode yang ada pada file ‘shell.c’. Sebagai catatan lainnya juga bahwa output aplikasi dari engine SQLite tidak harus selalu berakhir berupa shell console, SQLite sendiri merupakan sebuah library independen yang menyediakan API (Application Programming Interface) sebagai sebuah jalur bagi para pengembang untuk mengembangkan aplikasi-aplikasi yang menggunakan fungsi-fungsi yang sudah disediakan untuk mengolah database SQLite dalam bentuk dan menggunakan bahasa pemograman apapun. Platform yang digunakan dapat bervariasi mulai dari desktop application, mobile application, bahkan hingga untuk kebutuhan web application. Sebagai contoh, ‘sqlite.c’ dapat dikompilasi menjadi sebuah file DLL (Dynamic Link Libraries) yang dimana nantinya dapat digunakan dan di-load pada sebuah desktop application yang dikembangkan dengan menggunakan platform VB.NET. Gambar berikut merupakan perbandingan yang ada untuk hasil executable yang sudah dikompilasi dari SQLite shell console antara versi sebelum modifikasi (tanpa operator DIVIDE) dan versi sesudah hasil modifikasi (dengan operator DIVIDE) :
101
Gambar 4.14 Perbandingan Sebelum dan Sesudah Modifikasi 1
Gambar tersebut merupakan tampilan awal sebagai layar utama dari aplikasi shell console untuk SQLite. Dapat terlihat di sini bahwa ada perbedaan pada tampilan versi yang tercetak dari engine library SQLite yang digunakan pada masing-masing gambar di mana pada versi hasil modifikasi terdapat tambahan frase ‘(extended with DIVIDE op)’.
102 Gambar berikutnya merupakan perbandingan selanjutnya di antara kedua aplikasi yang ada. Dapat terlihat pada gambar bahwa pada kedua aplikasi untuk query SELECT tanpa menggunakan operator DIVIDE dapat dieksekusi tanpa adanya syntax error. Namun pada aplikasi pertama (versi sebelum modifikasi) belum mendukung penggunaan operator DIVIDE (muncul pesan SQL error).
Gambar 4.15 Perbandingan Sebelum dan Sesudah Modifikasi 2
103 4.3.1 Pengujian Pengujian untuk query baru yang diusulkan (mengandung operator DIVIDE) akan dibandingkan terhadap query klasik yang merupakan alternatif
query yang ada di berbagai buku-buku literatur database yang umum dijumpai (Date 2000; Desai 1990; Elmasri 2000; Kroenke 2000; O’Neil 1999; Ramakrishnan 2000; Watson 1999) dan secara umum sering digunakan dan diajarkan secara akademis sebagai salah satu bentuk alternatif query untuk implementasi dari operasi Division. Faktor penguji yang akan dibandingkan terdiri atas dua jenis. Yang pertama adalah dari segi faktor performa terhadap query yang ada dengan dilihat berdasarkan waktu tempuh yang digunakan (elapsed time). Lalu yang kedua adalah dari segi faktor kemudahan pengunaan dengan indikator yang digunakan adalah dari banyaknya jumlah token / kata yang digunakan pada masing-masing query. Berikut merupakan beberapa kondisi dan kriteria yang digunakan untuk melakukan pengujian : •
Pengujian dilakukan dengan menggunakan struktur relasi (tabel) yang ada seperti pada contoh pada bab sebelumnya sebagai berikut : Supplier PK
SupplierID
SupplierPart PK,FK1 PK,FK2
SupplierID PartID
Part PK
PartID
Gambar 4.16 Contoh Struktur Relasi untuk Pengujian
104 •
Operasi Division yang dilakukan adalah sebagai berikut : SuppliertPart ÷ Part
•
Pengujian dilakukan sebanyak tiga kali (3x) dengan kondisi jumlah data (record) untuk setiap tabel yang bebeda dan variasi persebaran sampel yang berbeda-beda pada setiap pengujian.
•
Pengujian
dilakukan
pada
platform
mesin
yang
sama
dan
menggunakan satu aplikasi executable yang sama dengan spesifikasi mesin yang digunakan yaitu sebuah notebook-PC dengan prosesor Intel Core2Duo 2,66Ghz, RAM 3GB, dengan menggunakan OS Windows 7 32-bit. •
Pengujian yang dilakukan adalah dengan membandingkan kedua query berikut ini : select distinct supplierid from supplierpart as sp1 where not exists ( select * from part as p1 where not exists ( select * from supplierpart sp2 where sp1.supplierid = sp2.supplierid and sp2.partid = p1.partid ) )
Selanjutnya disebut sebagai Query 1 (Q1), dan
select * from supplierpart divide part using (partid)
sebagai Query 2 (Q2)
105 4.3.1.1
Pengujian #1
Gambar 4.17 Pengujian 1 Query Operasi Division Dengan dan Tanpa Menggunakan “DIVIDE”
Pengujian #1 seperti dapat terlihat pada Gambar 4.16 dilakukan dengan kondisi data di mana jumlah baris data yang ada pada tabel Supplier sebagai tabel yang mewakili maksimum variasi kolom SupplierID yaitu sebanyak 50 record, lalu untuk tabel Part sebagai maksimum variasi kolom PartID sekaligus sebagai tabel DIVISOR sebanyak 10 record. Kemudian untuk
SupplierPart sebagai tabel
DIVIDEND, isi record-nya merupakan hasil generasi secara otomatis dengan mengikutsertakan semua variasi kemungkinan yang ada pada kolom SupplierID maupun PartID dan didapatkan hasil acak sebanyak 221 record. Kondisi jumlah data seperti ini dapat dikategorikan sebagai kondisi dengan jumlah data yang terbilang masih sedikit. Setelah dieksekusi, Q1 dan Q2 mengembalikan nilai baris data yang sama namun dengan waktu tempuh yang berbeda, dengan Q1 selama
106 0,109 detik dan Q2 lebih unggul pada 0,016 detik. Walaupun secara persentase perbandingan kedua rentang waktu tersebut cukuplah signifikan (Q2 hampir 7x lebih cepat), namun pada penggunaan secara praktis mungkin tidak terlalu kentara perbedaannya sehingga secara praktikal kedua query tersebut masih boleh dikatakan seimbang. 4.3.1.2
Pengujian #2
Gambar 4.18 Pengujian 2 Query Operasi Division Dengan dan Tanpa Menggunakan “DIVIDE”
Pengujian #2 masih menggunakan cara yang sama dengan Pengujian #1 namun dengan penggunaan baris data yang ditingkatkan jumlahnya. Untuk tabel Supplier menjadi 100 record, tabel Part 50 record, dan generasi acak untuk tabel SupplierPart sebanyak 1.705 record. Setelah Q1 dan Q2 dieksekusi maka mulai terlihat perbedaan yang cukup signifikan bila dibandingkan dengan hasil pengujian sebelumnya. Pada taraf penggunaan jumlah baris data pada DIVIDEND yang mencapai nilai ribuan mulai menunjukkan titik lemah yang ada pada Q1.
107 Q1 membutuhkan waktu tempuh hingga 34,258 detik, yang dimana Q2, dengan waktu tempuh yang seakan konstan dibandingkan sebelumnya, hanya membutuhkan 0,106 detik (yang berarti 21x lebih cepat). Pada pengujian ini mulai terlihat bagaimana ketidakefisiennya Q1 seiring bertambahnya jumlah data, yang diakibatkan oleh penggunaan subquery yang bertingkat (NOT EXISTS dua rangkap). 4.3.1.3
Pengujian #3
Gambar 4.19 Pengujian 3 Query Operasi Division Dengan dan Tanpa Menggunakan “DIVIDE”
Pada Pengujian #3 kembali dilakukan dengan cara yang sama seperti pengujian-pengujian sebelumnya dan dengan terus meningkatkan jumlah data yang ada. Tabel Supplier 300 record, tabel Part 100 record, dan menghasilkan tabel SupplierPart sebanyak 10.305 record. Hasil pada pengujian kali ini mulai menunjukan perbedaan yang cukup ekstrem, dengan Q1 selama 2672,796 detik (± 45 menit!) dengan Q2 yang cukup dengan hanya selama 0,078 detik.
108 4.3.1.4
Ringkasan Pengujian Hasil dari ketiga pengujian di atas akan diringkas ke dalam bentuk tabel berikut ini :
Tabel 4.1 Hasil Pengujian Performa Query Operasi Division
#1
#2
#3
Jumlah Supplier
50
100
300
Jumlah Part
10
50
100
221
1.705
10.305
Jumlah SupplierPart ÷ Part
11
22
78
Waktu Tempuh Q1 (detik)
0,109
34,258
2.672,796
Waktu Tempuh Q2 (detik)
0,016
0,016
0,078
Jumlah SupplierPart
Efisiensi Waktu Q2 : Q1
681,25% 2.141,125% 34.266,61%
Jumlah token atau kata yang ada pada Q1 sebanyak : 44 buah, sedangkan Q2 sebanyak : 10 buah. Atau dengan kata lain Q2 memiliki 77.27% token lebih sedikit dibandingkan Q1. Berdasarkan hasil pengujian di atas maka dapat disimpulkan bahwa Q2 (query yang diusulkan) lebih unggul dari segi performa dengan waktu rata-rata lebih cepat 120x dibandingkan Q1 (query klasik) dan juga lebih mudah digunakan karena 77% lebih pendek dibanding Q1 sehingga penulisan query menjadi lebih ringkas dan mudah dibaca.
109 4.3.1.5
Pengujian Lanjutan Untuk mempertegas hasil pengujian sebelumnya, akan dilakukan pengujian lanjutan dengan kondisi dan kriteria pengujian yang sama namun dengan jumlah record data yang berbeda :
SupplierCount merupakan banyaknya data Supplier, PartCount merupakan banyaknya data Part, MaxSupplierPart merupakan banyaknya Part maksimum yang dapat disediakan oleh satu Supplier, ResultCount merupakan banyaknya baris dari hasil operasi Division, SupplierPartCount merupakan banyaknya baris DIVIDEND, Q1Time merupakan lamanya waktu yang diperlukan untuk memproses query Q1(Query yang merupakan solusi sementara), sedangkan Q2Time merupakan lamanya waktu yang diperlukan untuk memproses query Q2(Query dari hasil
110 implementasi operator Division yang diusulkan). Q1Time dan Q2Time dalam satuan detik. Hasil pengujian di atas dapat juga direpresentasikan dalam bentuk grafik sebagai berikut:
Gambar 4.20 Grafik Hasil Pengujian Lanjutan
Perbesaran Grafik Q2(Query yang diusulkan) 0.09 0.08