Apa dua kali lipat terdekat dengan 1.0, itu bukan 1.0?

88

Apakah ada cara untuk secara terprogram mendapatkan penggandaan yang paling dekat dengan 1.0, tetapi sebenarnya bukan 1.0?

Salah satu cara hacky untuk melakukan ini adalah dengan memcpy double menjadi integer berukuran sama, lalu kurangi satu. Cara kerja format floating-point IEEE754, ini akan berakhir dengan penurunan eksponen satu kali saat mengubah bagian pecahan dari semua nol (1,000000000000) menjadi semua satu (1,111111111111). Namun ada mesin di mana bilangan bulat disimpan little-endian sementara floating-point disimpan big-endian, jadi itu tidak akan selalu berfungsi.

jorgbrown
sumber
4
Anda tidak dapat berasumsi bahwa +1 adalah jarak yang sama (dari 1.0) dengan -1. Interleaving dari representasi floating point basis 10 dan basis 2 berarti bahwa celahnya tidak rata.
Richard Critten
2
@ Richard: Anda benar. Sangat tidak mungkin bahwa mengurangi satu ULP akan mendapatkan, eh, nilai "selanjutnya", karena saya kira eksponen harus disesuaikan juga. nextafter()adalah satu-satunya cara yang tepat untuk mencapai apa yang dia inginkan.
Rudy Velthuis
1
FYI memiliki membaca blog ini (bukan milikku): exploringbinary.com/...
Richard Critten
1
@ RudyVelthuis: Ini berfungsi pada setiap format titik mengambang biner IEEE754.
Edgar Bonet
1
Oke, lalu beri tahu saya: apa "yang berfungsi pada setiap format titik mengambang IEEE754"? Tidaklah benar bahwa jika Anda menurunkan signifikansi bahwa Anda mendapatkan nilai "firstbefore ()", terutama tidak untuk 1.0, yang memiliki signifikansi yang merupakan pangkat dua. Itu berarti bahwa 1.0000...biner adalah penurunan ke 0.111111....dan untuk menormalkannya, Anda harus menggesernya ke kiri: 1.11111...yang mengharuskan Anda menurunkan eksponen. Dan kemudian Anda berjarak 2 ulp dari 1.0. Jadi tidak, mengurangi satu dari nilai integral TIDAK memberi Anda apa yang ditanyakan di sini.
Rudy Velthuis

Jawaban:

23

Di C dan C ++, berikut ini memberikan nilai yang paling dekat dengan 1.0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Namun perlu diperhatikan bahwa dalam versi C ++, limits.htidak digunakan lagi dan digantikan climits. Namun, jika Anda tetap menggunakan kode khusus C ++, Anda dapat menggunakan

#include <limits>

typedef std::numeric_limits<double> lim_dbl;
double closest_to_1 = 1.0 - lim_dbl::epsilon()/lim_dbl::radix;

Dan seperti yang Jarod42 tulis dalam jawabannya, karena C99 atau C ++ 11 Anda juga dapat menggunakan nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Tentu saja di C ++ Anda dapat (dan untuk versi C ++ yang lebih baru harus) menyertakan cmathdan menggunakan std::nextaftersebagai gantinya.

celtschk
sumber
143

Sejak C ++ 11, Anda dapat menggunakan nextafteruntuk mendapatkan nilai yang dapat direpresentasikan berikutnya dalam arah tertentu:

std::nextafter(1., 0.); // 0.99999999999999989
std::nextafter(1., 2.); // 1.0000000000000002

Demo

Jarod42
sumber
11
Ini juga merupakan cara yang baik untuk kenaikan ganda ke integer representable berikutnya: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Johannes Schaub - litb
43
Pertanyaan berikutnya adalah "bagaimana ini diterapkan di stdlib": P
Lightness Races di Orbit
17
Setelah membaca komentar @ LightnessRacesinOrbit, saya jadi penasaran. Beginilah cara glibc mengimplementasikannextafter , dan beginilah cara musl mengimplementasikannya jika ada orang lain yang ingin melihat cara melakukannya. Pada dasarnya: memutar-mutar bit mentah.
Cornstalks
2
@ Cornstalks: Saya tidak terkejut karena sedikit memutar-mutar, satu-satunya pilihan lain adalah memiliki dukungan CPU.
Matthieu M.
5
Sedikit memutarbalikkan adalah satu-satunya cara untuk melakukannya dengan benar, IMO. Anda dapat melakukan banyak percobaan percobaan, mencoba mendekatinya secara perlahan, tetapi itu bisa sangat lambat.
Rudy Velthuis
25

Di C, Anda dapat menggunakan ini:

#include <float.h>
...
double value = 1.0+DBL_EPSILON;

DBL_EPSILON adalah perbedaan antara 1 dan nilai terkecil lebih besar dari 1 yang dapat direpresentasikan.

Anda harus mencetaknya menjadi beberapa digit untuk melihat nilai sebenarnya.

Di platform saya, printf("%.16lf",1.0+DBL_EPSILON)berikan 1.0000000000000002.

barak manos
sumber
10
Sehingga tidak akan bekerja untuk beberapa nilai selain 1.sebagai 1'000'000 Demo
Jarod42
7
@ Jarod42: Anda benar, tetapi OP menanyakan secara spesifik 1.0. BTW, ini juga memberikan nilai terdekat lebih besar dari 1, dan bukan nilai terdekat absolut ke 1 (yang mungkin lebih kecil dari 1). Jadi saya setuju bahwa ini adalah jawaban parsial, tetapi saya pikir itu tetap bisa berkontribusi.
barak manos
@ LưuVĩnhPhúc: Saya memberikan ketepatan tentang batasan jawaban, dan yang paling dekat ke arah lain.
Jarod42
7
Ini tidak memberikan penggandaan terdekat ke 1.0, karena (dengan asumsi basis 2) penggandaan tepat sebelum 1.0 hanya berjarak setengah dari penggandaan tepat setelah 1.0 (yang Anda hitung).
celtschk
@celtschk: Anda benar, saya sudah menjelaskannya di komentar di atas.
barak manos
4

Di C ++ Anda juga bisa menggunakan ini

1 + std::numeric_limits<double>::epsilon()
phuclv.dll
sumber
1
Seperti jawaban barak manos, ini tidak akan berfungsi untuk nilai apa pun selain 1.
zwol
2
@zwol secara teknis untuk implementasi floating-point biner tipikal ini akan bekerja untuk nilai apa pun antara 1 dan 2-epsilon. Tapi, ya, Anda benar bahwa itu hanya dijamin berlaku untuk 1.
Random832
7
Secara teknis, ini tidak berfungsi untuk 1, karena angka yang paling dekat dengan 1 adalah angka yang tepat sebelum 1, bukan yang tepat setelahnya. presisi ganda antara 0,5 dan 1 dua kali lebih tinggi dari presisi antara 1 dan 2, oleh karena itu angka tepat sebelum 1 berakhir mendekati 1.
HelloGoodbye