Number.DiyFp.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Diagnostics;
  5. using System.Numerics;
  6. namespace System
  7. {
  8. internal static partial class Number
  9. {
  10. // This is a port of the `DiyFp` implementation here: https://github.com/google/double-conversion/blob/a711666ddd063eb1e4b181a6cb981d39a1fc8bac/double-conversion/diy-fp.h
  11. // The backing structure and how it is used is described in more detail here: http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf
  12. // This "Do It Yourself Floating Point" class implements a floating-point number with a ulong significand and an int exponent.
  13. // Normalized DiyFp numbers will have the most significant bit of the significand set.
  14. // Multiplication and Subtraction do not normalize their results.
  15. // DiyFp are not designed to contain special doubles (NaN and Infinity).
  16. internal readonly ref struct DiyFp
  17. {
  18. public const int DoubleImplicitBitIndex = 52;
  19. public const int SingleImplicitBitIndex = 23;
  20. public const int SignificandSize = 64;
  21. public readonly ulong f;
  22. public readonly int e;
  23. // Computes the two boundaries of value.
  24. //
  25. // The bigger boundary (mPlus) is normalized.
  26. // The lower boundary has the same exponent as mPlus.
  27. //
  28. // Precondition:
  29. // The value encoded by value must be greater than 0.
  30. public static DiyFp CreateAndGetBoundaries(double value, out DiyFp mMinus, out DiyFp mPlus)
  31. {
  32. var result = new DiyFp(value);
  33. result.GetBoundaries(DoubleImplicitBitIndex, out mMinus, out mPlus);
  34. return result;
  35. }
  36. // Computes the two boundaries of value.
  37. //
  38. // The bigger boundary (mPlus) is normalized.
  39. // The lower boundary has the same exponent as mPlus.
  40. //
  41. // Precondition:
  42. // The value encoded by value must be greater than 0.
  43. public static DiyFp CreateAndGetBoundaries(float value, out DiyFp mMinus, out DiyFp mPlus)
  44. {
  45. var result = new DiyFp(value);
  46. result.GetBoundaries(SingleImplicitBitIndex, out mMinus, out mPlus);
  47. return result;
  48. }
  49. public DiyFp(double value)
  50. {
  51. Debug.Assert(double.IsFinite(value));
  52. Debug.Assert(value > 0.0);
  53. f = ExtractFractionAndBiasedExponent(value, out e);
  54. }
  55. public DiyFp(float value)
  56. {
  57. Debug.Assert(float.IsFinite(value));
  58. Debug.Assert(value > 0.0f);
  59. f = ExtractFractionAndBiasedExponent(value, out e);
  60. }
  61. public DiyFp(ulong f, int e)
  62. {
  63. this.f = f;
  64. this.e = e;
  65. }
  66. public DiyFp Multiply(in DiyFp other)
  67. {
  68. // Simply "emulates" a 128-bit multiplication
  69. //
  70. // However: the resulting number only contains 64-bits. The least
  71. // signficant 64-bits are only used for rounding the most significant
  72. // 64-bits.
  73. uint a = (uint)(f >> 32);
  74. uint b = (uint)(f);
  75. uint c = (uint)(other.f >> 32);
  76. uint d = (uint)(other.f);
  77. ulong ac = ((ulong)(a) * c);
  78. ulong bc = ((ulong)(b) * c);
  79. ulong ad = ((ulong)(a) * d);
  80. ulong bd = ((ulong)(b) * d);
  81. ulong tmp = (bd >> 32) + (uint)(ad) + (uint)(bc);
  82. // By adding (1UL << 31) to tmp, we round the final result.
  83. // Halfway cases will be rounded up.
  84. tmp += (1U << 31);
  85. return new DiyFp((ac + (ad >> 32) + (bc >> 32) + (tmp >> 32)), (e + other.e + SignificandSize));
  86. }
  87. public DiyFp Normalize()
  88. {
  89. // This method is mainly called for normalizing boundaries.
  90. //
  91. // We deviate from the reference implementation by just using
  92. // our LeadingZeroCount function so that we only need to shift
  93. // and subtract once.
  94. Debug.Assert(f != 0);
  95. int lzcnt = BitOperations.LeadingZeroCount(f);
  96. return new DiyFp((f << lzcnt), (e - lzcnt));
  97. }
  98. // The exponents of both numbers must be the same.
  99. // The significand of 'this' must be bigger than the significand of 'other'.
  100. // The result will not be normalized.
  101. public DiyFp Subtract(in DiyFp other)
  102. {
  103. Debug.Assert(e == other.e);
  104. Debug.Assert(f >= other.f);
  105. return new DiyFp((f - other.f), e);
  106. }
  107. private void GetBoundaries(int implicitBitIndex, out DiyFp mMinus, out DiyFp mPlus)
  108. {
  109. mPlus = new DiyFp(((f << 1) + 1), (e - 1)).Normalize();
  110. // The boundary is closer if the sigificand is of the form:
  111. // f == 2^p-1
  112. //
  113. // Think of v = 1000e10 and v- = 9999e9
  114. // Then the boundary == (v - v-) / 2 is not just at a distance of 1e9 but at a distance of 1e8.
  115. // The only exception is for the smallest normal, where the largest denormal is at the same distance as its successor.
  116. //
  117. // Note: denormals have the same exponent as the smallest normals.
  118. // We deviate from the reference implementation by just checking if the significand has only the implicit bit set.
  119. // In this scenario, we know that all the explicit bits are 0 and that the unbiased exponent is non-zero.
  120. if (f == (1UL << implicitBitIndex))
  121. {
  122. mMinus = new DiyFp(((f << 2) - 1), (e - 2));
  123. }
  124. else
  125. {
  126. mMinus = new DiyFp(((f << 1) - 1), (e - 1));
  127. }
  128. mMinus = new DiyFp((mMinus.f << (mMinus.e - mPlus.e)), mPlus.e);
  129. }
  130. }
  131. }
  132. }