Nhân đôi gần nhất với 1.0, đó không phải là 1.0 là gì?


88

Có cách nào lập trình để lấy số nhân đôi gần nhất với 1.0, nhưng thực tế không phải là 1.0 không?

Một cách khó để làm điều này là ghi nhớ nhân đôi thành một số nguyên có cùng kích thước, và sau đó trừ đi một. Cách thức hoạt động của các định dạng dấu phẩy động IEEE754, điều này sẽ làm giảm số mũ đi một trong khi thay đổi phần phân số từ tất cả các số không (1.000000000000) thành tất cả các số (1.111111111111). Tuy nhiên, có những máy mà số nguyên được lưu trữ little-endian trong khi dấu phẩy động được lưu trữ big-endian, vì vậy điều đó không phải lúc nào cũng hoạt động.


4
Bạn không thể cho rằng +1 là cùng khoảng cách (từ 1,0) với -1. Sự xen kẽ của biểu diễn dấu phẩy động cơ số 10 và cơ số 2 có nghĩa là các khoảng trống không đồng đều.
Richard viết

2
@Richard: bạn nói đúng. Rất ít khả năng trừ đi một ULP sẽ nhận được giá trị, er, "nextbefore", vì tôi đoán số mũ cũng sẽ phải được điều chỉnh. nextafter()là cách thích hợp duy nhất để đạt được những gì anh ta muốn.
Rudy Velthuis

1
FYI có một đọc của blog này (không phải của tôi): exploringbinary.com/...
Richard Critten

1
@RudyVelthuis: Nó hoạt động trên mọi định dạng dấu chấm động nhị phân IEEE754.
Edgar Bonet

1
Ok, sau đó cho tôi biết: "hoạt động trên mọi định dạng dấu phẩy động IEEE754" là gì? Đơn giản là không đúng nếu bạn giảm ý nghĩa và bạn nhận được giá trị "firstbefore ()", đặc biệt là không đúng với 1,0, giá trị này có ý nghĩa và là lũy thừa của hai. Điều đó có nghĩa là 1.0000...hệ nhị phân giảm xuống 0.111111....và để chuẩn hóa nó, bạn phải chuyển nó sang trái: 1.11111...điều này yêu cầu bạn giảm số mũ. Và sau đó bạn cách 1,0 ulp. Vì vậy, không, trừ đi một trong giá trị tích phân KHÔNG cung cấp cho bạn những gì được yêu cầu ở đây.
Rudy Velthuis

Câu trả lời:


22

Trong C và C ++, giá trị sau cho giá trị gần nhất với 1,0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

Tuy nhiên, lưu ý rằng trong các phiên bản C ++ sau này, limits.hnó không được dùng nữa climits. Nhưng sau đó, nếu bạn đang sử dụng mã cụ thể C ++, bạn có thể sử dụng

#include <limits>

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

Và như Jarod42 viết trong câu trả lời của mình, kể từ C99 hoặc C ++ 11, bạn cũng có thể sử dụng nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

Tất nhiên trong C ++, bạn có thể (và đối với các phiên bản C ++ sau này nên) bao gồm cmathvà sử dụng std::nextafterthay thế.


143

Kể từ C ++ 11, bạn có thể sử dụng nextafterđể nhận giá trị có thể biểu diễn tiếp theo theo hướng nhất định:

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

Bản giới thiệu


11
Đây cũng là một cách hay để tăng một đôi đến số nguyên biểu diễn tiếp theo: std::ceil(std::nextafter(1., std::numeric_limits<double>::max())).
Johannes Schaub - litb

43
Câu hỏi tiếp theo của sẽ là "làm thế nào là thực hiện trong stdlib": P
Lightness Races ở Orbit

17
Sau khi đọc bình luận của @ LightnessRacesinOrbit, tôi rất tò mò. Đây là cách glibc dụng cụnextafter , và đây là cách musl cụ nó trong trường hợp bất cứ ai khác muốn xem làm thế nào nó được thực hiện. Về cơ bản: nguyên bit twiddling.
Cornstalks

2
@Cornstalks: Tôi không ngạc nhiên khi nó rơi vào tình trạng lộn xộn, lựa chọn khác duy nhất là có hỗ trợ CPU.
Matthieu M.

5
Xoay bit là cách duy nhất để làm điều đó đúng cách, IMO. Bạn có thể thử rất nhiều lần thử, cố gắng từ từ tiếp cận nó, nhưng điều đó có thể rất chậm.
Rudy Velthuis

25

Trong C, bạn có thể sử dụng cái này:

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

DBL_EPSILON là sự khác biệt giữa 1 và giá trị nhỏ nhất lớn hơn 1 có thể đại diện được.

Bạn sẽ cần in nó thành nhiều chữ số để xem giá trị thực.

Trên nền tảng của tôi, printf("%.16lf",1.0+DBL_EPSILON)cho 1.0000000000000002.


10
Vì vậy mà công việc sẽ không cho một số giá trị khác hơn 1.1'000'000 Demo
Jarod42

7
@ Jarod42: Bạn nói đúng, nhưng OP hỏi cụ thể về 1.0. BTW, nó cũng cho giá trị gần nhất lớn hơn 1 chứ không phải giá trị gần nhất tuyệt đối với 1 (có thể nhỏ hơn 1). Vì vậy, tôi đồng ý rằng đây là câu trả lời một phần, nhưng tôi nghĩ nó vẫn có thể đóng góp.
barak manos

@ LưuVĩnhPhúc: Mình đưa ra độ chính xác về hạn chế của đáp án, và gần nhất theo hướng khác.
Jarod42

7
Điều này không cung cấp cho nhân đôi gần nhất với 1,0, vì (giả sử cơ số 2) nhân đôi ngay trước 1,0 chỉ bằng một nửa so với nhân đôi ngay sau 1,0 (là kết quả bạn tính toán).
celtschk

@celtschk: Bạn nói đúng, tôi đã giải thích điều đó trong nhận xét ở trên.
barak manos

4

Trong C ++, bạn cũng có thể sử dụng

1 + std::numeric_limits<double>::epsilon()

1
Giống như câu trả lời barak Manos', điều này sẽ không làm việc cho bất kỳ giá trị khác hơn 1.
Zwol

2
@zwol tehally đối với các triển khai dấu phẩy động nhị phân điển hình, nó sẽ hoạt động với bất kỳ giá trị nào từ 1 đến 2-epsilon. Nhưng, vâng, bạn nói đúng rằng nó chỉ đảm bảo để áp dụng đối với 1.
Random832

7
Về mặt kỹ thuật, nó không hoạt động với số 1, vì số gần nhất với 1 là số ngay trước số 1, không phải số ngay sau nó. đôi chính xác của từ 0,5 đến 1 là cao gấp đôi độ chính xác của nó giữa 1 và 2, vì thế mà số ngay trước khi 1 đầu lên gần gũi hơn với 1.
HelloGoodbye
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.