Tính toán mạnh mẽ của giá trị trung bình của hai số trong dấu phẩy động?


15

Cho x, ylà hai số dấu phẩy động. Cách đúng để tính nghĩa của chúng là gì?

Cách ngây thơ (x+y)/2có thể dẫn đến tràn khi xyquá lớn. Tôi nghĩ 0.5 * x + 0.5 * ycó thể tốt hơn, nhưng nó liên quan đến hai phép nhân (có thể không hiệu quả), và tôi không chắc liệu nó có đủ tốt hay không. Có cách nào tốt hơn?

Một ý tưởng khác mà tôi đã chơi với là (y/2)(1 + x/y)nếu x<=y. Nhưng một lần nữa, tôi không chắc làm thế nào để phân tích điều này và chứng minh rằng nó đáp ứng yêu cầu của tôi.

Hơn nữa, tôi cần một sự đảm bảo rằng giá trị trung bình được tính sẽ là >= min(x,y)<= max(x,y). Như đã chỉ ra trong câu trả lời của Don Hatch , có lẽ cách tốt hơn để đặt ra câu hỏi này là: Việc triển khai giá trị trung bình của hai số luôn cho kết quả chính xác nhất có thể là gì? Đó là, nếu xylà các số có dấu phẩy động, làm thế nào để tính số của dấu phẩy động gần nhất với (x+y)/2? Trong trường hợp này, giá trị trung bình được tính là tự động >= min(x,y)<= max(x,y). Xem câu trả lời của Don hatch để biết chi tiết.

Lưu ý: Ưu tiên của tôi là độ chính xác mạnh mẽ. Hiệu quả là chi tiêu. Tuy nhiên, nếu có nhiều thuật toán mạnh mẽ và chính xác, tôi sẽ chọn cách hiệu quả nhất.


(+1) Câu hỏi thú vị, đáng ngạc nhiên là không tầm thường.
Kirill

1
Trước đây, các giá trị dấu phẩy động được tính toán và giữ ở dạng chính xác cao hơn cho kết quả trung gian. Nếu a + b (nhân đôi 64 bit) tạo ra kết quả trung gian 80 bit và đây là số được chia cho 2, bạn không phải lo lắng về việc tràn. Mất độ chính xác là ít rõ ràng.
JDługosz

Giải pháp cho vấn đề này có vẻ tương đối đơn giản ( tôi đã thêm một câu trả lời ). Có một điều là tôi là một lập trình viên chứ không phải một chuyên gia về khoa học máy tính, vậy tôi còn thiếu điều gì khiến câu hỏi này trở nên khó khăn hơn nhiều?
IQAndreas

Đừng lo lắng về chi phí nhân và chia cho hai; trình biên dịch của bạn sẽ tối ưu hóa chúng cho bạn.
Federico Poloni

Câu trả lời:


18

Tôi nghĩ tính chính xác và tính ổn định của thuật toán số của Higham giải quyết cách thức người ta có thể phân tích các loại vấn đề này. Xem Chương 2, đặc biệt là bài tập 2.8.

Trong câu trả lời này, tôi muốn chỉ ra điều gì đó không thực sự được đề cập trong cuốn sách của Higham (dường như nó không được biết đến rộng rãi cho vấn đề đó). Nếu bạn quan tâm đến việc chứng minh các thuộc tính của các thuật toán số đơn giản như các thuật toán này, bạn có thể sử dụng sức mạnh của các bộ giải SMT hiện đại ( Lý thuyết Modulo thỏa mãn ), chẳng hạn như z3 , sử dụng gói như sbv trong Haskell. Điều này có phần dễ dàng hơn so với sử dụng bút chì và giấy.

Giả sử tôi đã cho rằng và tôi muốn biết liệu z = ( x + y ) / 2 có thỏa mãn x z y không . Mã Haskell sau đây0xyz= =(x+y)/2xzy

import Data.SBV

test1 :: (SFloat -> SFloat -> SFloat) -> Symbolic SBool
test1 fun =
  do [x, y] <- sFloats ["x", "y"]
     constrain $ bnot (isInfiniteFP x) &&& bnot (isInfiniteFP y)
     constrain $ 0 .<= x &&& x .<= y
     let z = fun x y
     return $ x .<= z &&& z .<= y

test2 :: (SFloat -> SFloat -> SFloat) -> Symbolic SBool
test2 fun =
  do [x, y] <- sFloats ["x", "y"]
     constrain $ bnot (isInfiniteFP x) &&& bnot (isInfiniteFP y)
     constrain $ x .<= y
     let z = fun x y
     return $ x .<= z &&& z .<= y

sẽ để tôi làm điều này tự động . Dưới đây test1 funđề xuất rằng cho tất cả hữu hạn nổi x , y với 0 x y .xfbạnn(x,y)yx,y0xy

λ> prove $ test1 (\x y -> (x + y) / 2)
Falsifiable. Counter-example:
  x = 2.3089316e36 :: Float
  y = 3.379786e38 :: Float

Nó tràn ra. Giả sử bây giờ tôi dùng công thức khác của bạn: z= =x/2+y/2

λ> prove $ test1 (\x y -> x/2 + y/2)
Falsifiable. Counter-example:
  x = 2.3509886e-38 :: Float
  y = 2.3509886e-38 :: Float

Không hoạt động (do dòng chảy dần dần: , có thể không trực quan do tất cả số học là cơ sở-2).(x/2)×2x

Bây giờ hãy thử :z= =x+(y-x)/2

λ> prove $ test1 (\x y -> x + (y-x)/2)
Q.E.D.

Làm! Đây Q.E.D.là một bằng chứng cho thấy test1tài sản giữ cho tất cả các phao như được định nghĩa ở trên.

Điều gì giống nhau, nhưng bị giới hạn ở (thay vì 0 x y )?xy0xy

λ> prove $ test2 (\x y -> x + (y-x)/2)
Falsifiable. Counter-example:
  x = -3.1300826e34 :: Float
  y = 3.402721e38 :: Float

Được rồi, vậy nếu tràn ra, thì z = x + ( y / 2 - x / 2 ) thì sao?y-xz= =x+(y/2-x/2)

λ> prove $ test2 (\x y -> x + (y/2 - x/2))
Q.E.D.

Vì vậy, có vẻ như trong số các công thức tôi đã thử ở đây, dường như hoạt động (với một bằng chứng, quá). Cách tiếp cận của người giải quyết SMT dường như là một cách nhanh chóng hơn để trả lời những nghi ngờ về các công thức dấu phẩy động đơn giản hơn là phân tích lỗi dấu phẩy động bằng bút chì và giấy.x+(y/2-x/2)

Cuối cùng, mục tiêu của sự chính xác và ổn định thường mâu thuẫn với mục tiêu hiệu suất. Về hiệu suất, tôi thực sự không thấy làm thế nào bạn có thể làm tốt hơn , đặc biệt là vì trình biên dịch vẫn sẽ thực hiện rất nhiều việc dịch điều này thành hướng dẫn máy cho bạn.(x+y)/2

PS Đây là tất cả với chính xác đơn IEEE754 dấu chấm động số học. Tôi đã kiểm tra với số học có độ chính xác kép (thay thế bằng ) và nó cũng hoạt động.xx+(y/2-x/2)ySFloatSDouble

-ffast-math(x+y)/2

PPPS Tôi đã mang đi một chút chỉ nhìn vào các biểu thức đại số đơn giản mà không có điều kiện. Công thức của Don Hatch hoàn toàn tốt hơn.


2
Giữ lấy; bạn có cho rằng nếu x <= y (bất kể x> = 0 hay không) thì x + (y / 2-x / 2) là một cách tốt để làm điều đó? Dường như với tôi điều đó không thể đúng, vì nó đưa ra câu trả lời sai trong trường hợp sau đây khi câu trả lời có thể biểu diễn chính xác: x = -1, y = 1 + 2 ^ -52 (số đại diện nhỏ nhất lớn hơn 1), trong trường hợp đó, câu trả lời là 2 ^ -53. Xác nhận bằng trăn: >>> x = -1.; y = 1.+2.**-52; print `2**-53`, `(x+y)/2.`, `x+(y/2.-x/2.)`
Don nở

2
x(x+y)/2yx,y(x+y)/2(x+y)/2

8

Đầu tiên, hãy quan sát rằng nếu bạn có một phương pháp đưa ra câu trả lời chính xác nhất trong mọi trường hợp, thì nó sẽ đáp ứng điều kiện cần thiết của bạn. (Lưu ý rằng tôi nói một câu trả lời chính xác nhất chứ không phải là những câu trả lời chính xác nhất, vì có thể có hai người chiến thắng.) Chứng minh: Nếu, ngược lại, bạn có một câu trả lời chính xác-như-thể mà không đáp ứng các điều kiện cần thiết, mà có nghĩa là answer<min(x,y)<=max(x,y)(trong trường hợp nào min(x,y)là câu trả lời tốt hơn, mâu thuẫn) hoặc min(x,y)<=max(x,y)<answer(trong trường hợp đó max(x,y)là câu trả lời tốt hơn, mâu thuẫn).

Vì vậy, tôi nghĩ rằng điều đó có nghĩa là câu hỏi của bạn sôi sục để tìm ra một câu trả lời chính xác nhất có thể. Giả sử số học của IEEE754 trong suốt, tôi đề xuất như sau:

if max(abs(x),abs(y)) >= 1.:
    return x/2. + y/2.
else:
    return (x+y)/2.

Lập luận của tôi rằng điều này đưa ra một câu trả lời chính xác nhất là một phân tích trường hợp hơi tẻ nhạt. Đây là:

  • Trường hợp max(abs(x),abs(y)) >= 1.:

    • Subcase không x và y không được chuẩn hóa: Trong trường hợp này, câu trả lời được tính toán x/2.+y/2.điều khiển cùng một mantissas và do đó đưa ra câu trả lời chính xác giống như tính toán (x+y)/2sẽ mang lại nếu chúng ta giả sử số mũ mở rộng để ngăn tràn. Câu trả lời này có thể phụ thuộc vào chế độ làm tròn nhưng trong mọi trường hợp, nó được đảm bảo bởi IEEE754 là câu trả lời tốt nhất có thể (từ thực tế là tính toán x+yđược đảm bảo là xấp xỉ tốt nhất với toán học x + y và phép chia cho 2 là chính xác trường hợp).
    • Subcase x là không chuẩn hóa (và vì vậy abs(y)>=1):

      answer = x/2. + y/2. = y/2. since abs(x/2.) is so tiny compared to abs(y/2.) = the exact mathematical value of y/2 = a best possible answer.

    • Subcase y là không chuẩn hóa (và vì vậy abs(x)>=1): tương tự.

  • Trường hợp max(abs(x),abs(y)) < 1.:
    • Subcase tính toán x+ylà không chuẩn hóa hoặc không chuẩn hóa và "thậm chí": Mặc dù tính toán x+ycó thể không chính xác, nhưng nó được đảm bảo bởi IEEE754 là xấp xỉ tốt nhất có thể với toán học x + y. Trong trường hợp này, phép chia tiếp theo cho 2 trong biểu thức (x+y)/2.là chính xác, vì vậy câu trả lời (x+y)/2.được tính là một phép tính gần đúng nhất có thể với toán học (x + y) / 2.
    • Subcase tính toán x+ylà không chuẩn hóa và "lẻ": Trong trường hợp này chính xác là một trong x, y cũng phải được chuẩn hóa-và- "lẻ", có nghĩa là khác của x, y được không chuẩn hóa với dấu ngược lại, và do đó, tính toán x+ylà chính xác là toán học x + y, và do đó, tính toán (x+y)/2.được đảm bảo bởi IEEE754 là một xấp xỉ tốt nhất có thể với toán học (x + y) / 2.

Tôi nhận ra khi tôi nói "không chuẩn hóa" Tôi thực sự có ý gì đó khác - nghĩa là, các số gần nhau như các số có được, tức là phạm vi các số lớn gấp đôi phạm vi của các số không chuẩn hóa, tức là 8 dấu đầu tiên hoặc hơn trong sơ đồ tại en.wikipedia.org/wiki/Den normal_number . Vấn đề là, những số "lẻ" trong số này là những số duy nhất chia hai số đó không chính xác. Tôi cần phải diễn đạt lại phần này của câu trả lời để làm rõ điều này.
Don nở

ftôi(op(x,y))= =op(x,y)(1+δ)|δ|bạnx/2+y/2(x+y)/2luôn luôn được làm tròn một cách chính xác, vắng mặt trên / dưới, tất cả những gì còn lại là không hiển thị gì quá mức / quá mức, điều này thật dễ dàng.
Kirill

@Kirill Tôi hơi lạc lối ... bạn đến từ đâu? Ngoài ra tôi không nghĩ hoàn toàn đúng là "chia cho 2 là chính xác cho các số không bất thường" ... đây là điều tương tự tôi đã vấp phải, và có vẻ hơi khó xử khi cố gắng làm cho đúng. Tuyên bố chính xác là một cái gì đó giống như "x / 2 chính xác miễn là abs (x) ít nhất gấp đôi số không bình thường lớn nhất" ... thật là khó xử!
Don nở

3

Đối với các định dạng dấu phẩy động nhị phân IEEE-754, được minh họa bằng binary64tính toán (độ chính xác kép), S. Boldo đã chính thức chứng minh rằng thuật toán đơn giản hiển thị dưới đây mang lại mức trung bình được làm tròn chính xác.

Sylvie Boldo, "Xác minh chính thức các chương trình tính toán mức trung bình dấu phẩy động." Trong hội nghị quốc tế về phương pháp kỹ thuật chính thức , trang 17-32. Springer, Cham, 2015. ( dự thảo trực tuyến )

(x+y)/2x/2+y/2binary64C[2-967,2970]C để cung cấp hiệu suất tốt nhất cho một trường hợp sử dụng cụ thể.

Điều này mang lại ISO-C99mã mẫu sau đây :

double average (double x, double y) 
{
    const double C = 1; /* 0x1p-967 <= C <= 0x1p970 */
    return (C <= fabs (x)) ? (x / 2 + y / 2) : ((x + y) / 2);
}

Trong công việc tiếp theo gần đây, S. Boldo và các đồng tác giả đã chỉ ra cách đạt được kết quả tốt nhất có thể cho các định dạng dấu phẩy động thập phân IEEE-754 bằng cách sử dụng các phép toán đa nhân (FMA) hợp nhất và độ chính xác nổi tiếng- nhân đôi khối xây dựng (TwoSum):

Sylvie Boldo, Florian Faissole và Vincent Tourneur, "Một thuật toán được chứng minh chính thức để tính trung bình chính xác của các số dấu phẩy động thập phân." Trong Hội nghị chuyên đề số 25 về số học máy tính (ARITH 25) , tháng 6 năm 2018, trang 69-75. ( dự thảo trực tuyến )


2

Mặc dù nó có thể không hiệu quả siêu hiệu quả, nhưng có một cách rất đơn giản để (1) đảm bảo không có con số nào lớn hơn xhoặc y(không tràn) và (2) giữ điểm nổi là "chính xác" như có thể (và (3) , như một phần thưởng bổ sung, mặc dù phép trừ đang được sử dụng, sẽ không có giá trị nào được lưu dưới dạng số âm.

float difference = max(x, y) - min(x, y);
return min(x, y) + (difference / 2.0);

Trong thực tế, nếu bạn thực sự muốn đi cho chính xác, bạn thậm chí không cần phải thực hiện phân chia tại chỗ; chỉ cần trả về các giá trị min(x, y)differencemà bạn có thể sử dụng để đơn giản hóa logic hoặc thao tác sau này.


Điều tôi đang cố gắng tìm ra bây giờ là làm thế nào để làm cho cùng một câu trả lời này hoạt động với nhiều hơn hai mục , trong khi giữ cho tất cả các biến ở mức thấp hơn số lớn nhất và chỉ sử dụng một thao tác chia để duy trì độ chính xác.
IQAndreas

@becko Yup, bạn sẽ làm chia ít nhất hai lần. Ngoài ra, ví dụ bạn đưa ra sẽ làm cho câu trả lời sai. Hãy tưởng tượng ý nghĩa của 2,4,9, nó không giống như ý nghĩa của 3,9.
IQAndreas

Bạn nói đúng, đệ quy của tôi đã sai. Tôi không chắc cách khắc phục ngay bây giờ mà không mất độ chính xác.
vẫy gọi

Bạn có thể chứng minh rằng điều này cho kết quả chính xác nhất có thể? Đó là, nếu xylà dấu phẩy động, tính toán của bạn tạo ra một dấu phẩy động gần nhất với (x+y)/2?
vẫy gọi

1
Sẽ không tràn này khi x, y là số có thể biểu thị nhỏ nhất và lớn nhất?
Don nở

1

Chuyển đổi sang mức cao hơn, thêm các giá trị ở đó và chuyển đổi trở lại.

Không nên có tràn trong giới hạn cao hơn và nếu cả hai đều nằm trong phạm vi dấu phẩy động hợp lệ, thì số được tính cũng sẽ nằm trong đó.

Và nó nên ở giữa chúng, trường hợp xấu nhất chỉ bằng một nửa số lượng lớn hơn nếu số lượng không đủ.


Đây là cách tiếp cận vũ phu. Nó có thể hoạt động, nhưng tôi đang tìm kiếm một phân tích không yêu cầu độ chính xác trung gian cao hơn. Ngoài ra, bạn có thể ước tính độ chính xác cao hơn cần thiết là bao nhiêu không? Trong mọi trường hợp, đừng xóa câu trả lời này (+1), tôi sẽ không chấp nhận nó là câu trả lời.
vẫy gọi

1

Về mặt lý thuyết, x/2có thể được tính bằng cách trừ 1 từ lớp phủ.

Tuy nhiên, thực sự triển khai các thao tác bitwise như thế này không nhất thiết phải đơn giản, đặc biệt nếu bạn không biết định dạng của các số dấu phẩy động của mình.

Nếu bạn có thể làm điều này, toàn bộ thao tác được giảm xuống còn 3 phép cộng / trừ, đây sẽ là một cải tiến đáng kể.


0

Tôi đã suy nghĩ cùng dòng với @Roland Heath nhưng chưa thể bình luận, đây là tôi:

x/2có thể được tính bằng cách trừ 1 từ số mũ (không phải mantissa, trừ 1 từ mantissa đang trừ đi 2^(value_of_exponent-length_of_mantissa)giá trị tổng thể).

Không hạn chế trường hợp chung, hãy giả sử x < y. (Nếu x > y, đặt lại tên cho các biến. Nếu x = y, (x+y) / 2là tầm thường.)

  • Chuyển đổi (x+y) / 2thành x/2 + y/2, có thể được thực hiện bằng hai phép trừ số nguyên (bằng một từ số mũ)
    • Tuy nhiên, có một giới hạn thấp hơn về số mũ tùy thuộc vào đại diện của bạn. Nếu số mũ của bạn là tối thiểu trước khi trừ 1, phương pháp này sẽ yêu cầu xử lý trường hợp đặc biệt. Số mũ tối thiểu trên xsẽ làm cho x/2nhỏ hơn mức có thể biểu diễn (giả sử mantissa được biểu thị bằng số 1 ẩn).
    • Thay vì trừ đi 1 từ số mũ của số một x, hãy thay đổi xvị trí của một bên (và thêm số 1 dẫn đầu ẩn, nếu có).
    • Trừ 1 từ số mũ của y, nếu nó không tối thiểu. Nếu nó tối thiểu (y lớn hơn x, vì mantissa), hãy dịch chuyển lớp phủ sang bên phải một (thêm ẩn dẫn 1, nếu có).
    • Thay đổi lớp phủ mới của xbên phải theo số mũ của y.
    • Thực hiện bổ sung số nguyên trên lớp phủ, trừ khi lớp phủ của xđã được chuyển ra hoàn toàn. Nếu cả hai số mũ đều ở mức tối thiểu, thì số mũ sẽ bị tràn, điều đó cũng không sao, bởi vì mức tràn đó được cho là sẽ trở thành một số dẫn đầu ngầm.
  • và một bổ sung điểm nổi.
    • Không thể nghĩ về bất kỳ trường hợp đặc biệt nào ở đây; ngoại trừ làm tròn, cũng áp dụng cho dịch chuyển được mô tả ở trên.
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.