x = 0.1
(x, x.hex(), x.as_integer_ratio())(0.1, '0x1.999999999999ap-4', (3602879701896397, 36028797018963968))
Setelah menyelesaikan unit ini, pembaca dapat:
ulp, epsilon mesin, serta batas luapan dan aliran-bawah;scipy.special pada domain yang dinyatakan;Prasyarat lokal: Unit 1, Unit 2, dan Unit 3; pangkat bilangan bulat, akar kuadrat, nilai mutlak, fungsi rasional, dan galat relatif pada tingkat A30. Tidak diperlukan kalkulus.
Pada lingkungan proyek ini, float memakai format biner 64-bit. Secara sederhana, bilangan berhingga normal disimpan dalam bentuk
Ada satu bit tanda, eksponen berhingga, dan 53 bit ketelitian signifikan jika angka satu di depan titik ikut dihitung. Karena hanya ada sejumlah pola bit, hanya sejumlah bilangan real yang dapat disimpan. Hasil operasi biasanya dibulatkan ke bilangan representabel terdekat.
Metode hex() memperlihatkan nilai biner sebuah float tanpa kehilangan informasi:
x = 0.1
(x, x.hex(), x.as_integer_ratio())(0.1, '0x1.999999999999ap-4', (3602879701896397, 36028797018963968))
Pecahan dari as_integer_ratio() adalah nilai rasional yang benar-benar tersimpan. Nilai itu dekat dengan , tetapi bukan .
ulp, epsilon, dan jarak yang berubahSatu unit in the last place (ulp) adalah jarak lokal antara bilangan representabel yang berdekatan. Jarak ini berubah mengikuti besar bilangan.
import math
import sys
(
sys.float_info.epsilon,
math.ulp(1.0),
math.ulp(float(2**53)),
)(2.220446049250313e-16, 2.220446049250313e-16, 2.0)
Di sekitar 1, ulp(1.0) sama dengan sys.float_info.epsilon, yaitu selisih antara 1 dan bilangan float berikutnya. Beberapa buku memakai istilah “epsilon mesin” untuk angka ini. Buku lain memakai unit roundoff dalam model pembulatan-ke-terdekat. Karena istilahnya tidak seragam, nyatakan definisi yang dipakai.
Di sekitar , satu ulp bernilai 2. Akibatnya:
besar = float(2**53)
(besar + 1.0 == besar, besar + 2.0 == besar + 2)(True, True)
Ini bukan kesalahan Python dalam menjumlahkan bilangan bulat. Masalahnya adalah hasil sedang dipaksa kembali ke kisi float64 yang pada skala itu berjarak 2.
Ketelitian dan rentang adalah dua batas berbeda. sys.float_info.max adalah bilangan float berhingga terbesar. Mengalikannya dengan 2 menghasilkan takhingga positif pada lingkungan yang diuji. Di ujung kecil, sys.float_info.min adalah bilangan positif normal terkecil, bukan bilangan positif terkecil.
Bilangan subnormal mengisi sebagian celah menuju nol dengan ketelitian yang berkurang. math.ulp(0.0) memberi subnormal positif terkecil. Membaginya dua memberi nol karena tidak ada bilangan positif representabel yang lebih kecil.
terbesar = sys.float_info.max
subnormal_terkecil = math.ulp(0.0)
(
terbesar * 2.0,
sys.float_info.min,
subnormal_terkecil,
subnormal_terkecil / 2.0,
)(inf, 2.2250738585072014e-308, 5e-324, 0.0)
Periksa nilai dengan math.isfinite sebelum hasil takhingga atau nol akibat aliran-bawah diteruskan ke tahap berikutnya. Namun, pemeriksaan setelah kejadian tidak mengganti analisis skala sebelum komputasi.
Pengurangan dua bilangan hampir sama dapat menghapus sebagian besar angka signifikan. Untuk besar, pertimbangkan
Pada , penjumlahan x + 1.0 sudah membulat kembali ke x. Pengurangan langsung lalu menghasilkan nol. Sebelum menghitung, rasionalkan secara aljabar:
x = 1e16
langsung = math.sqrt(x + 1.0) - math.sqrt(x)
stabil = 1.0 / (math.sqrt(x + 1.0) + math.sqrt(x))
(x + 1.0 == x, langsung, stabil)(True, 0.0, 5e-09)
Bentuk kedua menghindari pengurangan dua akar yang hampir sama. Reformulasi ini berasal dari identitas aljabar pada bilangan real; program Unit 6 membandingkan kedua implementasi dengan referensi Decimal berketelitian tinggi. Referensi berketelitian tinggi tetap merupakan alat pemeriksaan, bukan definisi bukti yang berdiri sendiri.
exprel stabil dekat nolSciPy menyediakan implementasi khusus bagi fungsi
Domain matematisnya adalah seluruh bilangan real setelah nilai di nol didefinisikan melalui perpanjangan kontinu tersebut. Lab rinci pada unit ini memakai domain komputasional yang lebih sempit, , karena galat mundur relatif yang dicatat membagi dengan . Oracle Decimal tidak menghitung exp(x) - 1: ia menjumlahkan deret pangkat bagi dan tanpa pembatalan. Presisinya sekurang-kurangnya 80 digit dan bertambah menurut eksponen , dengan 50 digit penjaga setelah orde . Karena itu, koreksi pertama tetap terurai bahkan untuk subnormal binary64 terkecil. Batas atas dan pengecualian adalah batas laporan galat lab, bukan batas domain matematis exprel.
Evaluasi langsung menghitung exp(x), mengurangkan 1, lalu membagi dengan x. Ketika x kecil, exp(x) dan 1 hampir sama sehingga pengurangan dapat membuang angka signifikan. Pada nilai yang lebih ekstrem, exp(x) dapat dibulatkan tepat menjadi 1 dan pembilang naif menjadi nol.
import math
import scipy
from scipy import special
x = 1e-8
naive = (math.exp(x) - 1.0) / x
stable = float(special.exprel(x))
extreme_x = 1e-16
extreme_naive = (math.exp(extreme_x) - 1.0) / extreme_x
extreme_stable = float(special.exprel(extreme_x))
(scipy.__version__, naive, stable, extreme_naive, extreme_stable)('1.15.2', 0.999999993922529, 1.000000005, 0.0, 1.0)
Perbedaan itu tidak otomatis berarti masalah matematis berkondisi buruk. Ukuran pengondisian relatif lokal ialah
Untuk ,
Dekat nol, mendekati 1, mendekati , dan mendekati . Formula turunan ini dipakai sebagai alat diagnostik yang disediakan; menurunkannya bukan prasyarat A30. Pada , angka kondisi sekitar , jadi gangguan relatif kecil pada masukan seharusnya tidak diperbesar. Galat besar evaluasi naif karena itu merupakan ketidakstabilan algoritmik, bukan kepekaan inheren fungsi.
Skrip pendamping membandingkan kedua hasil dengan referensi deret Decimal berpresisi adaptif pada domain lab yang dinyatakan. Galat maju yang dicatat ialah
Untuk memberi pembacaan galat mundur, skrip memakai estimasi orde pertama
Ini bertanya berapa besar perubahan masukan yang kira-kira diperlukan agar keluaran terhitung menjadi keluaran tepat bagi masukan yang berubah. Estimasi itu bukan solusi invers eksak dan dilabeli demikian di JSON. Untuk , estimasi mundur relatif evaluasi naif lebih besar dari 1, sedangkan estimasi SciPy jauh di bawah . Galat maju SciPy juga berada di bawah secara relatif pada run yang diuji.
Hasil tersebut merupakan bukti kerja bagi nilai, versi SciPy, dan lingkungan yang direkam. Ia menunjukkan contoh konkret bahwa implementasi khusus lebih akurat daripada formula naif dekat nol. Ia tidak membuktikan bahwa scipy.special.exprel stabil untuk setiap bilangan real atau setiap backend. Klaim umum tetap memerlukan analisis algoritme, model pembulatan, dan penanganan rentang.
Misalkan masalah meminta dan program menghasilkan .
Untuk masalah , kuadrat keluaran memberi masukan yang akan membuat keluaran itu eksak. Karena itu, ukuran mundur relatif yang mudah diperiksa adalah
y_hat = math.sqrt(2.0)
residual = y_hat * y_hat - 2.0
(y_hat, residual)(1.4142135623730951, 4.440892098500626e-16)
Galat mundur kecil berarti hasil merupakan solusi tepat bagi masalah yang sedikit terganggu. Apakah galat majunya juga kecil bergantung pada pengondisian masalah.
Pengondisian adalah sifat pertanyaan matematika: seberapa besar keluaran berubah ketika masukan sedikit berubah? Stabilitas algoritmik adalah sifat cara menghitung: apakah pembulatan internal menambah galat jauh melampaui kepekaan masalah?
Untuk
masukan dekat 1 sangat peka. Pada , gangguan sekitar dapat diperbesar kira-kira kali dalam galat relatif keluaran. Ini adalah masalah berkondisi buruk di dekat titik singular . Mengganti bahasa pemrograman tidak dapat menghapus kepekaan matematis tersebut.
Sebaliknya, masalah untuk besar tidak memerlukan jawaban nol. Bentuk pengurangan langsung kehilangan informasi, sedangkan bentuk rasional mempertahankannya. Di sini perbedaan utamanya adalah stabilitas cara evaluasi.
Empat kemungkinan harus dibedakan:
| Masalah | Algoritme | Makna praktis |
|---|---|---|
| berkondisi baik | stabil | galat kecil biasanya diharapkan |
| berkondisi baik | tidak stabil | algoritme menyia-nyiakan informasi |
| berkondisi buruk | stabil | hasil tetap dapat peka terhadap data |
| berkondisi buruk | tidak stabil | kepekaan masalah dan algoritme menumpuk |
Penjumlahan titik-mengambang tidak asosiatif. Secara real,
Namun, algoritme naif yang menjumlahkan dari kiri kehilangan angka 1 sebelum pengurangan terakhir.
nilai = [1e16, 1.0, -1e16]
def jumlah_naif(data):
total = 0.0
for angka in data:
total += angka
return total
(
jumlah_naif(nilai),
jumlah_naif([1e16, -1e16, 1.0]),
math.fsum(nilai),
)(0.0, 1.0, 1.0)
math.fsum melacak bagian kecil yang hilang dengan lebih cermat dan memberi 1 untuk contoh ini. sum bawaan dapat memakai strategi yang lebih baik pada versi Python tertentu; karena itu, skrip juga merekam hasilnya tetapi tidak menjadikannya definisi algoritme naif. math.fsum umumnya lebih akurat bagi data bercampur skala, tetapi bukan janji bahwa setiap masalah penjumlahan bebas galat. Rekam metode, versi, urutan data, dan skala nilainya.
Tes kedekatan yang digunakan unit ini berbentuk
dengan anggaran absolut dan anggaran relatif. Bagian absolut penting di dekat nol; bagian relatif mengikuti skala referensi.
Anggaran harus berasal dari masalah sebelum hasil dilihat. Sumbernya dapat mencakup ketelitian pengukuran, pendekatan model, diskretisasi, penghentian iterasi, dan pembulatan. Penjumlahan batas tiap komponen memberi batas konservatif bila tidak ada analisis yang lebih tajam.
isclose atau fungsi sejenis hanya memeriksa aturan yang diberikan. Ia tidak memilih toleransi, tidak memperbaiki algoritme, dan tidak membuktikan bahwa referensi benar. Simpan nilai , , galat teramati, dan alasan pemilihannya.
Jalankan dari akar proyek:
python source/code/unit06_floating.py --output output/unit06-results.json
Skrip mencatat versi Python dan SciPy, profil binary64, pembatalan, perbandingan scipy.special.exprel, galat maju/mundur, pengondisian, penjumlahan, dan keputusan anggaran galat. Bilangan yang perlu dibedakan bit demi bit disimpan dengan repr, hex, atau teks Decimal. JSON memakai UTF-8, kunci terurut, inden tetap, dan akhir baris LF. Medan core_sha256 mengikat rekaman sebelum hash ditambahkan.
Eksperimen tersebut membuktikan perilaku kasus yang benar-benar dijalankan pada lingkungan yang dicatat. Ia dapat menunjukkan contoh penyangkal terhadap klaim seperti “urutan penjumlahan tidak pernah berpengaruh”. Ia tidak membuktikan teorema tentang semua operasi binary64.
Pernyataan umum memerlukan model, misalnya: setiap operasi normal dibulatkan ke nilai representabel terdekat dengan galat relatif paling besar sekitar , selama tidak terjadi luapan atau aliran-bawah. Dari model itu, batas galat dapat diturunkan langkah demi langkah. Tes lalu memeriksa apakah implementasi dan lingkungan mengikuti asumsi yang dicatat.
ulp dan kenaikan yang hilangDi sekitar , ulp bernilai 2. Jelaskan mengapa float(2**53) + 1.0 == float(2**53), tetapi penambahan 2 dapat menghasilkan bilangan berikutnya.
Bayangkan kisi bilangan representabel yang titik-titiknya berjarak 2.
Pada skala itu, bilangan float berhingga yang berdekatan adalah . Nilai real berada tepat di antara dua titik kisi dan dibulatkan menurut aturan ikatan ke nilai dengan bit terakhir genap, yaitu . Nilai sendiri representabel, sehingga kenaikan 2 tidak hilang.
Turunkan bentuk stabil untuk dan jelaskan mengapa bentuk itu lebih baik untuk besar.
Kalikan pembilang dan penyebut dengan jumlah kedua akar.
Bentuk langsung mengurangkan dua bilangan besar yang hampir sama sehingga angka signifikan dapat hilang. Bentuk rasional membagi 4 dengan jumlah positif dan menghindari pengurangan tersebut. Domain real yang dibahas ialah .
Untuk pendekatan terhadap , hitung pendekatan galat maju absolut dan galat mundur relatif berdasarkan persamaan .
Gunakan dan hitung .
Galat maju absolut kira-kira . Karena , galat mundur relatif terhadap masukan 2 ialah
Angka pertama membandingkan keluaran dengan akar yang diinginkan; angka kedua menanyakan perubahan relatif pada masukan agar keluaran menjadi akar eksak.
Klasifikasikan dua gejala berikut: (a) nilai berubah besar ketika dekat 1 diganggu sedikit; (b) pengurangan dua akar yang hampir sama memberi nol, tetapi bentuk rasional memberi nilai bukan nol yang akurat.
Tanyakan apakah kepekaan sudah ada pada fungsi matematika atau muncul dari urutan operasi.
Gejala (a) terutama merupakan pengondisian buruk: fungsi mempunyai titik singular pada 1 dan memang peka di dekatnya. Algoritme stabil pun tidak dapat menghilangkan kepekaan data itu. Gejala (b) terutama merupakan ketidakstabilan evaluasi langsung: masalah yang sama dapat dihitung jauh lebih akurat setelah reformulasi aljabar. Dalam pekerjaan nyata, keduanya dapat terjadi bersamaan.
Untuk data [1e16, 1.0, -1e16], target matematisnya 1. Bandingkan hasil jumlah_naif di atas dan math.fsum. Dengan anggaran absolut 0,5 dan anggaran relatif , tentukan mana yang lolos. Apakah satu keberhasilan membuktikan math.fsum selalu eksak?
Gunakan batas dengan .
jumlah_naif memberi 0 karena angka 1 hilang ketika ditambahkan ke . math.fsum memberi 1 pada kasus ini. Batas yang diizinkan adalah ; galat algoritme naif adalah 1 sehingga gagal, sedangkan galat math.fsum adalah 0 sehingga lolos. Keberhasilan ini hanya membuktikan hasil kasus tersebut pada lingkungan yang diuji; ia tidak membuktikan bahwa math.fsum selalu eksak untuk setiap daftar.
exprelJalankan scipy_exprel_report(1e-8) dari kode pendamping. Catat domain matematis dan domain lab, angka kondisi relatif, galat maju relatif kedua implementasi, serta estimasi galat mundur relatifnya. Tentukan apakah hasil naif yang buruk terutama menunjukkan masalah berkondisi buruk atau algoritme yang tidak stabil. Ulangi perbandingan nilai pada x=1e-16 dan batasi klaim Anda pada bukti yang benar-benar dijalankan.
Jika angka kondisi jauh di bawah 1 tetapi galat maju naif besar, masalahnya bukan penguatan gangguan masukan. Periksa juga apakah math.exp(1e-16) == 1.0.
Dari akar proyek, jalankan:
python tests/test_unit06.py Unit06FloatingTests.test_scipy_special_exprel_is_stable_near_zero -v
Cek mengikat domain lab, versi/fungsi SciPy, galat maju, estimasi galat mundur, pengondisian, pembatalan ekstrem, dan batas bukti. Tes regresi terpisah juga menjalankan subnormal binary64 terkecil dan memastikan oracle masih menangkap koreksi pertama , bukan membulatkannya diam-diam menjadi nol.
import runpy
from pprint import pprint
unit06 = runpy.run_path("source/code/unit06_floating.py")
report = unit06["scipy_exprel_report"](1e-8)
ringkasan = {
"domain": report["mathematical_domain"],
"lab": report["laboratory_domain"],
"condition": report["conditioning"]["relative_condition_number"],
"forward": report["forward_error"],
"backward": report["backward_error"],
"extreme": report["extreme_cancellation"],
"proof": report["evidence_boundary"],
}
pprint(ringkasan, sort_dicts=True, width=58){'backward': {'is_exact_inverse_solution': False,
'method': 'aproksimasi orde pertama '
'abs(delta_x) ~= '
"abs(delta_y)/abs(E'(x))",
'naive_absolute_estimate': '2.21549418274780203244E-8',
'naive_relative_estimate': '2.21549418274780198609E+0',
'scipy_absolute_estimate': '9.41080427077268199700E-17',
'scipy_relative_estimate': '9.41080427077268180010E-9'},
'condition': '5.00000000833333343795E-9',
'domain': 'semua x real',
'extreme': {'math_exp_x_equals_one': True,
'naive': '0.0',
'scipy_special_exprel': '1.0',
'x': '1e-16'},
'forward': {'naive_absolute': '1.10774709875888165308E-8',
'naive_relative': '1.10774709322014616851E-8',
'scipy_absolute': '4.70540216675568868538E-17',
'scipy_is_more_accurate': True,
'scipy_relative': '4.70540214322867789081E-17'},
'lab': '0 < |x| <= 1e-4 untuk masukan binary64',
'proof': {'claim': 'Pelaksanaan ini menunjukkan '
'pembatalan dan perbaikan pada nilai '
'yang dicatat; klaim umum memerlukan '
'analisis algoritme dan model '
'pembulatan.',
'executed_case_only': True,
'proves_stability_for_all_real_inputs': False}}
Domain matematisnya semua bilangan real dengan ; lab dibatasi pada bagi masukan binary64. Pada , angka kondisi sekitar , tetapi galat maju relatif naif lebih besar dari dan estimasi galat mundur relatifnya lebih besar dari 1. Sebaliknya, galat maju relatif SciPy di bawah dan estimasi mundurnya di bawah . Karena masalahnya berkondisi baik dekat nol, selisih tersebut menunjukkan pembatalan pada algoritme naif.
Pada , math.exp(x) membulat menjadi 1 sehingga formula naif memberi 0, sedangkan scipy.special.exprel memberi 1 pada run ini. Tes tersebut tidak membuktikan perilaku semua masukan atau backend; ia hanya memeriksa kasus dan lingkungan yang dicatat.
float membentuk kisi biner berhingga; ulp berubah mengikuti skala.scipy.special.exprel memberi contoh fungsi khusus yang dapat dijalankan dan menghindari pembatalan naif dekat nol pada domain lab yang dinyatakan.