ByteSize.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. using System;
  2. using System.Globalization;
  3. namespace Optimizer
  4. {
  5. /// <summary>
  6. /// Represents a byte size value with support for decimal (KiloByte) and
  7. /// binary values (KibiByte).
  8. /// </summary>
  9. public partial struct ByteSize : IComparable<ByteSize>, IEquatable<ByteSize>
  10. {
  11. public static readonly ByteSize MinValue = ByteSize.FromBits(long.MinValue);
  12. public static readonly ByteSize MaxValue = ByteSize.FromBits(long.MaxValue);
  13. public const long BitsInByte = 8;
  14. public const string BitSymbol = "b";
  15. public const string ByteSymbol = "B";
  16. public long Bits { get; private set; }
  17. public double Bytes { get; private set; }
  18. public string LargestWholeNumberBinarySymbol
  19. {
  20. get
  21. {
  22. // Absolute value is used to deal with negative values
  23. if (Math.Abs(this.PebiBytes) >= 1)
  24. return PebiByteSymbol;
  25. if (Math.Abs(this.TebiBytes) >= 1)
  26. return TebiByteSymbol;
  27. if (Math.Abs(this.GibiBytes) >= 1)
  28. return GibiByteSymbol;
  29. if (Math.Abs(this.MebiBytes) >= 1)
  30. return MebiByteSymbol;
  31. if (Math.Abs(this.KibiBytes) >= 1)
  32. return KibiByteSymbol;
  33. if (Math.Abs(this.Bytes) >= 1)
  34. return ByteSymbol;
  35. return BitSymbol;
  36. }
  37. }
  38. public string LargestWholeNumberDecimalSymbol
  39. {
  40. get
  41. {
  42. // Absolute value is used to deal with negative values
  43. if (Math.Abs(this.PetaBytes) >= 1)
  44. return PetaByteSymbol;
  45. if (Math.Abs(this.TeraBytes) >= 1)
  46. return TeraByteSymbol;
  47. if (Math.Abs(this.GigaBytes) >= 1)
  48. return GigaByteSymbol;
  49. if (Math.Abs(this.MegaBytes) >= 1)
  50. return MegaByteSymbol;
  51. if (Math.Abs(this.KiloBytes) >= 1)
  52. return KiloByteSymbol;
  53. if (Math.Abs(this.Bytes) >= 1)
  54. return ByteSymbol;
  55. return BitSymbol;
  56. }
  57. }
  58. public double LargestWholeNumberBinaryValue
  59. {
  60. get
  61. {
  62. // Absolute value is used to deal with negative values
  63. if (Math.Abs(this.PebiBytes) >= 1)
  64. return this.PebiBytes;
  65. if (Math.Abs(this.TebiBytes) >= 1)
  66. return this.TebiBytes;
  67. if (Math.Abs(this.GibiBytes) >= 1)
  68. return this.GibiBytes;
  69. if (Math.Abs(this.MebiBytes) >= 1)
  70. return this.MebiBytes;
  71. if (Math.Abs(this.KibiBytes) >= 1)
  72. return this.KibiBytes;
  73. if (Math.Abs(this.Bytes) >= 1)
  74. return this.Bytes;
  75. return this.Bits;
  76. }
  77. }
  78. public double LargestWholeNumberDecimalValue
  79. {
  80. get
  81. {
  82. // Absolute value is used to deal with negative values
  83. if (Math.Abs(this.PetaBytes) >= 1)
  84. return this.PetaBytes;
  85. if (Math.Abs(this.TeraBytes) >= 1)
  86. return this.TeraBytes;
  87. if (Math.Abs(this.GigaBytes) >= 1)
  88. return this.GigaBytes;
  89. if (Math.Abs(this.MegaBytes) >= 1)
  90. return this.MegaBytes;
  91. if (Math.Abs(this.KiloBytes) >= 1)
  92. return this.KiloBytes;
  93. if (Math.Abs(this.Bytes) >= 1)
  94. return this.Bytes;
  95. return this.Bits;
  96. }
  97. }
  98. public ByteSize(long bits)
  99. : this()
  100. {
  101. Bits = bits;
  102. Bytes = bits / BitsInByte;
  103. }
  104. public ByteSize(double bytes)
  105. : this()
  106. {
  107. // Get ceiling because bits are whole units
  108. Bits = (long)Math.Ceiling(bytes * BitsInByte);
  109. Bytes = bytes;
  110. }
  111. public static ByteSize FromBits(long value)
  112. {
  113. return new ByteSize(value);
  114. }
  115. public static ByteSize FromBytes(double value)
  116. {
  117. return new ByteSize(value);
  118. }
  119. /// <summary>
  120. /// Converts the value of the current object to a string.
  121. /// The prefix symbol (bit, byte, kilo, mebi, gibi, tebi) used is the
  122. /// largest prefix such that the corresponding value is greater than or
  123. /// equal to one.
  124. /// </summary>
  125. public override string ToString()
  126. {
  127. return this.ToString("0.##", CultureInfo.CurrentCulture);
  128. }
  129. public string ToString(string format)
  130. {
  131. return this.ToString(format, CultureInfo.CurrentCulture);
  132. }
  133. public string ToString(string format, IFormatProvider provider, bool useBinaryByte = false)
  134. {
  135. if (!format.Contains("#") && !format.Contains("0"))
  136. format = "0.## " + format;
  137. if (provider == null) provider = CultureInfo.CurrentCulture;
  138. Func<string, bool> has = s => format.IndexOf(s, StringComparison.CurrentCultureIgnoreCase) != -1;
  139. Func<double, string> output = n => n.ToString(format, provider);
  140. // Binary
  141. if (has("PiB"))
  142. return output(this.PebiBytes);
  143. if (has("TiB"))
  144. return output(this.TebiBytes);
  145. if (has("GiB"))
  146. return output(this.GibiBytes);
  147. if (has("MiB"))
  148. return output(this.MebiBytes);
  149. if (has("KiB"))
  150. return output(this.KibiBytes);
  151. // Decimal
  152. if (has("PB"))
  153. return output(this.PetaBytes);
  154. if (has("TB"))
  155. return output(this.TeraBytes);
  156. if (has("GB"))
  157. return output(this.GigaBytes);
  158. if (has("MB"))
  159. return output(this.MegaBytes);
  160. if (has("KB"))
  161. return output(this.KiloBytes);
  162. // Byte and Bit symbol must be case-sensitive
  163. if (format.IndexOf(ByteSize.ByteSymbol) != -1)
  164. return output(this.Bytes);
  165. if (format.IndexOf(ByteSize.BitSymbol) != -1)
  166. return output(this.Bits);
  167. if (useBinaryByte)
  168. {
  169. return string.Format("{0} {1}", this.LargestWholeNumberBinaryValue.ToString(format, provider), this.LargestWholeNumberBinarySymbol);
  170. }
  171. else
  172. {
  173. return string.Format("{0} {1}", this.LargestWholeNumberDecimalValue.ToString(format, provider), this.LargestWholeNumberDecimalSymbol);
  174. }
  175. }
  176. public override bool Equals(object value)
  177. {
  178. if (value == null)
  179. return false;
  180. ByteSize other;
  181. if (value is ByteSize)
  182. other = (ByteSize)value;
  183. else
  184. return false;
  185. return Equals(other);
  186. }
  187. public bool Equals(ByteSize value)
  188. {
  189. return this.Bits == value.Bits;
  190. }
  191. public override int GetHashCode()
  192. {
  193. return this.Bits.GetHashCode();
  194. }
  195. public int CompareTo(ByteSize other)
  196. {
  197. return this.Bits.CompareTo(other.Bits);
  198. }
  199. public ByteSize Add(ByteSize bs)
  200. {
  201. return new ByteSize(this.Bytes + bs.Bytes);
  202. }
  203. public ByteSize AddBits(long value)
  204. {
  205. return this + FromBits(value);
  206. }
  207. public ByteSize AddBytes(double value)
  208. {
  209. return this + ByteSize.FromBytes(value);
  210. }
  211. public ByteSize Subtract(ByteSize bs)
  212. {
  213. return new ByteSize(this.Bytes - bs.Bytes);
  214. }
  215. public static ByteSize operator +(ByteSize b1, ByteSize b2)
  216. {
  217. return new ByteSize(b1.Bytes + b2.Bytes);
  218. }
  219. public static ByteSize operator ++(ByteSize b)
  220. {
  221. return new ByteSize(b.Bytes + 1);
  222. }
  223. public static ByteSize operator -(ByteSize b)
  224. {
  225. return new ByteSize(-b.Bytes);
  226. }
  227. public static ByteSize operator -(ByteSize b1, ByteSize b2)
  228. {
  229. return new ByteSize(b1.Bytes - b2.Bytes);
  230. }
  231. public static ByteSize operator --(ByteSize b)
  232. {
  233. return new ByteSize(b.Bytes - 1);
  234. }
  235. public static bool operator ==(ByteSize b1, ByteSize b2)
  236. {
  237. return b1.Bits == b2.Bits;
  238. }
  239. public static bool operator !=(ByteSize b1, ByteSize b2)
  240. {
  241. return b1.Bits != b2.Bits;
  242. }
  243. public static bool operator <(ByteSize b1, ByteSize b2)
  244. {
  245. return b1.Bits < b2.Bits;
  246. }
  247. public static bool operator <=(ByteSize b1, ByteSize b2)
  248. {
  249. return b1.Bits <= b2.Bits;
  250. }
  251. public static bool operator >(ByteSize b1, ByteSize b2)
  252. {
  253. return b1.Bits > b2.Bits;
  254. }
  255. public static bool operator >=(ByteSize b1, ByteSize b2)
  256. {
  257. return b1.Bits >= b2.Bits;
  258. }
  259. public static ByteSize Parse(string s)
  260. {
  261. return Parse(s, NumberFormatInfo.CurrentInfo);
  262. }
  263. public static ByteSize Parse(string s, IFormatProvider formatProvider)
  264. {
  265. return Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, formatProvider);
  266. }
  267. public static ByteSize Parse(string s, NumberStyles numberStyles, IFormatProvider formatProvider)
  268. {
  269. // Arg checking
  270. if (string.IsNullOrWhiteSpace(s))
  271. throw new ArgumentNullException("s", "String is null or whitespace");
  272. // Get the index of the first non-digit character
  273. s = s.TrimStart(); // Protect against leading spaces
  274. var num = 0;
  275. var found = false;
  276. var numberFormatInfo = NumberFormatInfo.GetInstance(formatProvider);
  277. var decimalSeparator = Convert.ToChar(numberFormatInfo.NumberDecimalSeparator);
  278. var groupSeparator = Convert.ToChar(numberFormatInfo.NumberGroupSeparator);
  279. // Pick first non-digit number
  280. for (num = 0; num < s.Length; num++)
  281. if (!(char.IsDigit(s[num]) || s[num] == decimalSeparator || s[num] == groupSeparator))
  282. {
  283. found = true;
  284. break;
  285. }
  286. if (found == false)
  287. throw new FormatException($"No byte indicator found in value '{s}'.");
  288. int lastNumber = num;
  289. // Cut the input string in half
  290. string numberPart = s.Substring(0, lastNumber).Trim();
  291. string sizePart = s.Substring(lastNumber, s.Length - lastNumber).Trim();
  292. // Get the numeric part
  293. double number;
  294. if (!double.TryParse(numberPart, numberStyles, formatProvider, out number))
  295. throw new FormatException($"No number found in value '{s}'.");
  296. // Get the magnitude part
  297. switch (sizePart)
  298. {
  299. case "b":
  300. if (number % 1 != 0) // Can't have partial bits
  301. throw new FormatException($"Can't have partial bits for value '{s}'.");
  302. return FromBits((long)number);
  303. case "B":
  304. return FromBytes(number);
  305. }
  306. switch (sizePart.ToLowerInvariant())
  307. {
  308. // Binary
  309. case "kib":
  310. return FromKibiBytes(number);
  311. case "mib":
  312. return FromMebiBytes(number);
  313. case "gib":
  314. return FromGibiBytes(number);
  315. case "tib":
  316. return FromTebiBytes(number);
  317. case "pib":
  318. return FromPebiBytes(number);
  319. // Decimal
  320. case "kb":
  321. return FromKiloBytes(number);
  322. case "mb":
  323. return FromMegaBytes(number);
  324. case "gb":
  325. return FromGigaBytes(number);
  326. case "tb":
  327. return FromTeraBytes(number);
  328. case "pb":
  329. return FromPetaBytes(number);
  330. default:
  331. throw new FormatException($"Bytes of magnitude '{sizePart}' is not supported.");
  332. }
  333. }
  334. public static bool TryParse(string s, out ByteSize result)
  335. {
  336. try
  337. {
  338. result = Parse(s);
  339. return true;
  340. }
  341. catch
  342. {
  343. result = new ByteSize();
  344. return false;
  345. }
  346. }
  347. public static bool TryParse(string s, NumberStyles numberStyles, IFormatProvider formatProvider, out ByteSize result)
  348. {
  349. try
  350. {
  351. result = Parse(s, numberStyles, formatProvider);
  352. return true;
  353. }
  354. catch
  355. {
  356. result = new ByteSize();
  357. return false;
  358. }
  359. }
  360. }
  361. }