2013-07-24 31 views
5

EDIT: Để nó hoạt động ngay bây giờ, trong khi bình thường hóa mantiss, điều quan trọng là trước tiên hãy đặt bit ngầm, khi giải mã bit ngầm thì không cần phải thêm vào. Tôi đã để lại câu trả lời được đánh dấu là chính xác, vì thông tin đó thực sự hữu ích.Chuẩn hóa Mantissa của C# double

Tôi hiện đang triển khai mã hóa (Quy tắc mã hóa phân biệt) và có vấn đề nhỏ mã hóa giá trị kép.

Vì vậy, tôi có thể nhận ra các dấu hiệu, mũ và mantissa từ một đôi trong C# bằng cách sử dụng:

// get parts 
double value = 10.0; 
long bits = BitConverter.DoubleToInt64Bits(value); 
// Note that the shift is sign-extended, hence the test against -1 not 1 
bool negative = (bits < 0); 
int exponent = (int)((bits >> 52) & 0x7ffL); 
long mantissa = bits & 0xfffffffffffffL; 

(sử dụng mã từ here). Các giá trị này có thể được mã hóa và quá trình đảo ngược đơn giản của quy trình sẽ giúp tôi quay lại giá trị gốc ban đầu.

Tuy nhiên, các quy tắc mã hóa DER xác định rằng mantissa nên bình thường:

Trong Rules Encoding Canonical và các quy tắc Encoding sắc bình thường được chỉ định và mantissa (trừ khi nó là 0) cần để được dịch chuyển nhiều lần cho đến khi bit ít quan trọng nhất là 1.

(xem here in section 8.5.6.5).

Việc làm này bằng tay sử dụng:

while ((mantissa & 1) == 0) 
{ 
    mantissa >>= 1; 
    exponent++; 
} 

sẽ không làm việc, và mang lại cho tôi những giá trị kỳ lạ. (Ngay cả khi sử dụng toàn bộ hàm Jon Skeet được đăng trong liên kết đã nói ở trên).

Tôi dường như thiếu một thứ gì đó ở đây, sẽ dễ nhất nếu tôi lần đầu tiên có thể bình thường hóa mantiassa của đôi và nhận được "bit". Tuy nhiên, tôi cũng không thể hiểu tại sao việc bình thường hóa bằng tay sẽ không hoạt động chính xác.

Nhờ sự giúp đỡ,

Danny

EDIT: Vấn đề làm việc thực tế cho thấy vấn đề của tôi với bình thường mantiss:

static void Main(string[] args) 
    { 
     Console.WriteLine(CalculateDouble(GetBits(55.5, false))); 
     Console.WriteLine(CalculateDouble(GetBits(55.5, true))); 
     Console.ReadLine(); 
    } 

    private static double CalculateDouble(Tuple<bool, int, long> bits) 
    { 
     double result = 0; 
     bool isNegative = bits.Item1; 
     int exponent = bits.Item2; 
     long significand = bits.Item3; 

     if (exponent == 2047 && significand != 0) 
     { 
      // special case 
     } 
     else if (exponent == 2047 && significand == 0) 
     { 
      result = isNegative ? double.NegativeInfinity : double.PositiveInfinity; 
     } 
     else if (exponent == 0) 
     { 
      // special case, subnormal numbers 
     } 
     else 
     { 
      /* old code, wont work double actualSignificand = significand*Math.Pow(2,     
       -52) + 1; */ 
      double actualSignificand = significand*Math.Pow(2, -52); 
      int actualExponent = exponent - 1023; 
      if (isNegative) 
      { 
       result = actualSignificand*Math.Pow(2, actualExponent); 
      } 
      else 
      { 
       result = -actualSignificand*Math.Pow(2, actualExponent);**strong text** 
      } 
     } 
     return result; 

    } 


    private static Tuple<bool, int, long> GetBits(double d, bool normalizeSignificand) 
    { 
     // Translate the double into sign, exponent and mantissa. 
     long bits = BitConverter.DoubleToInt64Bits(d); 
     // Note that the shift is sign-extended, hence the test against -1 not 1 
     bool negative = (bits < 0); 
     int exponent = (int)((bits >> 52) & 0x7ffL); 
     long significand = bits & 0xfffffffffffffL; 

     if (significand == 0) 
     { 
      return Tuple.Create<bool, int, long>(false, 0, 0); 
     } 
     // fix: add implicit bit before normalization 
     if (exponent != 0) 
     { 
      significand = significand | (1L << 52); 
     } 
     if (normalizeSignificand) 
     { 
      //* Normalize */ 
      while ((significand & 1) == 0) 
      { 
       /* i.e., Mantissa is even */ 
       significand >>= 1; 
       exponent++; 
      } 
     } 
     return Tuple.Create(negative, exponent, significand); 

    } 
    Output: 
    55.5 
    2.25179981368527E+15 
+0

DoubleToInt64Bits() không đưa bạn định nghĩa, nó cung cấp cho bạn giá trị bằng số mũ đã được áp dụng. Mã đó chỉ là sai, vứt nó đi. –

+0

Oh, ok, tôi không biết rằng, tuy nhiên, bạn có một gợi ý làm thế nào tôi có thể có được những gì tôi muốn? Tôi nghĩ rằng bây giờ tôi sẽ thử một công đoàn (tốt, một cấu trúc với bố trí lĩnh vực rõ ràng) – Daniel

+0

Jon Skeet đang hỗ trợ câu trả lời đó, đăng một bình luận ở đó. Một liên minh không hoạt động, các bit này không rơi vào ranh giới byte. –

Trả lời

3

Khi bạn sử dụng BitConverter.DoubleToInt64Bits, nó mang lại cho bạn những giá trị double đã được mã hóa ở định dạng IEEE 754. Điều này có nghĩa là significand được mã hóa với một bit dẫn tiềm ẩn. (“Significand” là thuật ngữ ưu tiên cho phần thập phân của một giá trị dấu phẩy động và được sử dụng trong IEEE 754. Một định nghĩa là tuyến tính.Một mantissa là lôgarít. “Mantissa” bắt nguồn từ những ngày mà mọi người phải sử dụng logarit và giấy và các bảng chức năng để thực hiện các phép tính thô.) Để khôi phục lại các định nghĩa không được mã hóa, bạn sẽ phải khôi phục lại bit ngầm định.

Điều đó không khó. Khi bạn đã tách bit dấu, số mũ được mã hóa (dưới dạng số nguyên) và sau đó, đối với dấu phẩy được mã hóa 64 bit:

  • Nếu số mũ được mã hóa là tối đa (2047) và định nghĩa được mã hóa là khác 0, giá trị là một NaN. Có thông tin bổ sung trong các điều khoản và liệu NaN có đang báo hiệu hay không và các thông tin khác do người dùng thực hiện hoặc được xác định.
  • Nếu số mũ được mã hóa là số mũ lớn nhất và độ dài được mã hóa bằng 0, giá trị là vô cực (+ hoặc - theo dấu).
  • Nếu số mũ được mã hóa bằng 0, bit ngầm là 0, thì tên miền thực tế là số và được nhân với 2 –52 và số mũ thực tế là trừ đi độ lệch (1023) (so -1022).
  • Nếu không, bit ngầm định là một, nghĩa thực sự là từ được mã hóa và trước tiên nhân với 2 –52 rồi thêm vào một và số mũ thực là số mũ được mã hóa trừ đi độ lệch (1023).

(Nếu bạn muốn làm việc với số nguyên và không có phần cho significand, bạn có thể bỏ qua các phép nhân bằng 2 -52 và thêm -52 đến số mũ để thay thế. Trong trường hợp cuối cùng, significand là được thêm vào 2 thay vì một.)

Có một phương pháp thay thế tránh được BitConverter và mã hóa IEEE-754. Nếu bạn có thể gọi số frexp thường lệ từ C#, nó sẽ trả về phân số và số mũ bằng toán học thay vì mã hóa. Đầu tiên, xử lý zeroes, infinities, và NaNs một cách riêng biệt. Sau đó sử dụng:

int exponent; 
double fraction = frexp(value, &exponent); 

này đặt fraction đến một giá trị với cường độ trong [½, 1) và exponentfraction • 2 exponent bằng value. (Lưu ý rằng fraction vẫn có dấu, bạn có thể muốn tách biệt và sử dụng giá trị tuyệt đối.)

Tại thời điểm này, bạn có thể quy mô fraction như mong muốn (và điều chỉnh exponent tương ứng). Để mở rộng nó để nó là một số nguyên lẻ, bạn có thể nhân nó hai lần cho đến khi nó không có phần phân số.

+0

Cảm ơn bạn rất nhiều về những thông tin mở rộng này của Eric. Thông tin tốt đẹp về significand vs mantiss, tôi đoán tôi có thuật ngữ trong đầu từ uni. Tôi sẽ chấp nhận điều này là câu trả lời, vì nó có tất cả thông tin cần thiết, phần còn lại tùy thuộc vào tôi – Daniel

+0

Xin chào Eric, với thông tin của bạn bây giờ ít nhất tôi đang làm gì, nhưng việc bình thường hóa mantiss vẫn không hoạt động, tôi đoán Tôi có thể làm điều đó tại địa điểm sai, tôi đã chỉnh sửa câu hỏi của mình hiển thị ví dụ ... – Daniel

+0

@Daniel: 'CalculateDouble' lấy các bit của mã hóa IEEE-754 và chuyển đổi chúng thành' double'. Khi bạn "bình thường hóa" các meaningand trong 'GetBits', bạn đang tạo ra một mã hóa khác nhau. Kết quả của 'GetBits' là một dấu hiệu, một số mũ, và một định nghĩa, trong đó significand đã được" chuẩn hóa "bởi các quy tắc DER nhưng số mũ vẫn chứa độ lệch IEEE-754. Nếu thiên vị DER khác nhau, bạn cần phải trừ đi độ lệch IEEE-754 (1023) và thêm độ lệch DER.Bạn cũng cần phải trừ 52 để tính toán thực tế là significand là một số nguyên thay vì có điểm nhị phân lúc bắt đầu. –

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