2013-03-11 32 views
21

Là một phần của thử nghiệm đơn vị, tôi cần kiểm tra một số điều kiện biên. Một phương thức chấp nhận đối số System.Double.Nhận số đôi nhỏ nhất tiếp theo

Có cách nào để nhận được giá trị gấp đôi nhỏ nhất tiếp theo không? (ví dụ: giảm bớt phần định trị bằng 1 đơn vị-giá trị)?

Tôi xem xét sử dụng Double.Epsilon nhưng điều này không đáng tin cậy vì nó chỉ là đồng bằng nhỏ nhất từ ​​0 và do đó không hoạt động với các giá trị lớn hơn (ví dụ: 9999999999 - Double.Epsilon == 9999999999).

Vì vậy, các thuật toán hoặc mã cần thiết là những gì mà:

NextSmallest(Double d) < d 

... luôn luôn là sự thật.

+0

Còn nếu bạn chỉ chia cho 10 – Hogan

+4

Tôi nghĩ câu hỏi của bạn đã được trả lời tại đây: http://stackoverflow.com/a/2283565/1715579. –

Trả lời

14

Nếu con số của bạn là hữu hạn, bạn có thể sử dụng một vài phương pháp thuận tiện trong lớp BitConverter:

long bits = BitConverter.DoubleToInt64Bits(value); 
if (value > 0) 
    return BitConverter.Int64BitsToDouble(bits - 1); 
else if (value < 0) 
    return BitConverter.Int64BitsToDouble(bits + 1); 
else 
    return -double.Epsilon; 

IEEE -754 định dạng được thiết kế sao cho các bit tạo thành số mũ và mantissa cùng nhau tạo thành một số nguyên có cùng thứ tự như các số dấu phẩy động. Vì vậy, để có số nhỏ hơn lớn nhất, bạn có thể trừ một số từ số này nếu giá trị là dương và bạn có thể thêm một số nếu giá trị là số âm.

Lý do chính tại sao điều này hoạt động là bit hàng đầu của phần định trị không được lưu trữ. Nếu mantissa của bạn là tất cả số không, thì số của bạn là sức mạnh của hai. Nếu bạn trừ 1 từ kết hợp số mũ/mantissa, bạn nhận được tất cả những cái và bạn sẽ phải mượn từ các số mũ. Nói cách khác: bạn phải giảm số mũ, đó là chính xác những gì chúng ta muốn.

+0

Cảm ơn, tôi đã kết thúc bằng cách sử dụng phương pháp này và nó đã làm việc cho tôi – Dai

+0

Và còn về' NaN 'thì sao? cho họ? – m93a

3

Các trang Wikipedia trên đôi chính xác dấu chấm động là ở đây: http://en.wikipedia.org/wiki/Double_precision_floating-point_format

Đối với niềm vui, tôi đã viết một số mã để thoát ra khỏi sự biểu diễn nhị phân của định dạng double, decrements mantissa và recomposes các kết quả gấp đôi. Do bit ngầm trong phần định trị, chúng ta phải kiểm tra nó và sửa đổi số mũ cho phù hợp, và nó có thể thất bại gần các giới hạn.

Dưới đây là các mã:

public static double PrevDouble(double src) 
{ 
    // check for special values: 
    if (double.IsInfinity(src) || double.IsNaN(src)) 
     return src; 
    if (src == 0) 
     return -double.MinValue; 

    // get bytes from double 
    byte[] srcbytes = System.BitConverter.GetBytes(src); 

    // extract components 
    byte sign = (byte)(srcbytes[7] & 0x80); 
    ulong exp = ((((ulong)srcbytes[7]) & 0x7F) << 4) + (((ulong)srcbytes[6] >> 4) & 0x0F); 
    ulong mant = ((ulong)1 << 52) | (((ulong)srcbytes[6] & 0x0F) << 48) | (((ulong)srcbytes[5]) << 40) | (((ulong)srcbytes[4]) << 32) | (((ulong)srcbytes[3]) << 24) | (((ulong)srcbytes[2]) << 16) | (((ulong)srcbytes[1]) << 8) | ((ulong)srcbytes[0]); 

    // decrement mantissa 
    --mant; 

    // check if implied bit has been removed and shift if so 
    if ((mant & ((ulong)1 << 52)) == 0) 
    { 
     mant <<= 1; 
     exp--; 
    } 

    // build byte representation of modified value 
    byte[] bytes = new byte[8]; 
    bytes[7] = (byte)((ulong)sign | ((exp >> 4) & 0x7F)); 
    bytes[6] = (byte)((((ulong)exp & 0x0F) << 4) | ((mant >> 48) & 0x0F)); 
    bytes[5] = (byte)((mant >> 40) & 0xFF); 
    bytes[4] = (byte)((mant >> 32) & 0xFF); 
    bytes[3] = (byte)((mant >> 24) & 0xFF); 
    bytes[2] = (byte)((mant >> 16) & 0xFF); 
    bytes[1] = (byte)((mant >> 8) & 0xFF); 
    bytes[0] = (byte)(mant & 0xFF); 

    // convert back to double and return 
    double res = System.BitConverter.ToDouble(bytes, 0); 
    return res; 
} 

Tất cả mang đến cho bạn một giá trị khác với giá trị ban đầu của một sự thay đổi trong các bit thấp nhất của mantissa ... về mặt lý thuyết :)

Dưới đây là một thử nghiệm:

public static Main(string[] args) 
{ 
    double test = 1.0/3; 
    double prev = PrevDouble(test); 
    Console.WriteLine("{0:r}, {1:r}, {2:r}", test, prev, test - prev); 
} 

Cung cấp cho kết quả như sau trên máy tính của tôi:

0.33333333333333331, 0.33333333333333326, 5.5511151231257827E-17 

Sự khác biệt là có, nhưng có thể là dưới ngưỡng làm tròn. Khái niệm test == prev là false mặc dù, và có một sự khác biệt thực tế như trình bày ở trên :)

+2

Tôi khuyên bạn nên thay đổi chuỗi định dạng của bạn thành "{: r}, {1: r}, {2: r}" 'để in ra đôi trong định dạng" roundtrippable "để hiển thị sự khác biệt. Nó in: "0.33333333333333331, 0.33333333333333326, 5.5511151231257827E-17 " – porges

+0

Cảm ơn @ Porges, tôi sẽ sửa đổi câu trả lời :) – Corey

Các vấn đề liên quan