diff --git a/src/Humanizer.Tests.Shared/Bytes/ArithmeticTests.cs b/src/Humanizer.Tests.Shared/Bytes/ArithmeticTests.cs index 6d791c8e5..e44a2cf30 100644 --- a/src/Humanizer.Tests.Shared/Bytes/ArithmeticTests.cs +++ b/src/Humanizer.Tests.Shared/Bytes/ArithmeticTests.cs @@ -38,9 +38,18 @@ public void AddKilobytes() { var size = ByteSize.FromKilobytes(2).AddKilobytes(2); + Assert.Equal(4 * 1000 * 8, size.Bits); + Assert.Equal(4 * 1000, size.Bytes); + Assert.Equal(4, size.Kilobytes); + } + [Fact] + public void AddKibibytes() + { + var size = ByteSize.FromKibibytes(2).AddKibibytes(2); + Assert.Equal(4 * 1024 * 8, size.Bits); Assert.Equal(4 * 1024, size.Bytes); - Assert.Equal(4, size.Kilobytes); + Assert.Equal(4, size.Kibibytes); } [Fact] @@ -48,10 +57,21 @@ public void AddMegabytes() { var size = ByteSize.FromMegabytes(2).AddMegabytes(2); + Assert.Equal(4 * 1000 * 1000 * 8, size.Bits); + Assert.Equal(4 * 1000 * 1000, size.Bytes); + Assert.Equal(4 * 1000, size.Kilobytes); + Assert.Equal(4, size.Megabytes); + } + + [Fact] + public void AddMebibytes() + { + var size = ByteSize.FromMebibytes(2).AddMebibytes(2); + Assert.Equal(4 * 1024 * 1024 * 8, size.Bits); Assert.Equal(4 * 1024 * 1024, size.Bytes); - Assert.Equal(4 * 1024, size.Kilobytes); - Assert.Equal(4, size.Megabytes); + Assert.Equal(4 * 1024, size.Kibibytes); + Assert.Equal(4, size.Mebibytes); } [Fact] @@ -59,11 +79,23 @@ public void AddGigabytes() { var size = ByteSize.FromGigabytes(2).AddGigabytes(2); + Assert.Equal(4d * 1000 * 1000 * 1000 * 8, size.Bits); + Assert.Equal(4d * 1000 * 1000 * 1000, size.Bytes); + Assert.Equal(4d * 1000 * 1000, size.Kilobytes); + Assert.Equal(4d * 1000, size.Megabytes); + Assert.Equal(4d, size.Gigabytes); + } + + [Fact] + public void AddGibibytes() + { + var size = ByteSize.FromGibibytes(2).AddGibibytes(2); + Assert.Equal(4d * 1024 * 1024 * 1024 * 8, size.Bits); Assert.Equal(4d * 1024 * 1024 * 1024, size.Bytes); - Assert.Equal(4d * 1024 * 1024, size.Kilobytes); - Assert.Equal(4d * 1024, size.Megabytes); - Assert.Equal(4d, size.Gigabytes); + Assert.Equal(4d * 1024 * 1024, size.Kibibytes); + Assert.Equal(4d * 1024, size.Mebibytes); + Assert.Equal(4d, size.Gibibytes); } [Fact] @@ -71,12 +103,25 @@ public void AddTerabytes() { var size = ByteSize.FromTerabytes(2).AddTerabytes(2); + Assert.Equal(4d * 1000 * 1000 * 1000 * 1000 * 8, size.Bits); + Assert.Equal(4d * 1000 * 1000 * 1000 * 1000, size.Bytes); + Assert.Equal(4d * 1000 * 1000 * 1000, size.Kilobytes); + Assert.Equal(4d * 1000 * 1000, size.Megabytes); + Assert.Equal(4d * 1000, size.Gigabytes); + Assert.Equal(4d, size.Terabytes); + } + + [Fact] + public void AddTebibytes() + { + var size = ByteSize.FromTebibytes(2).AddTebibytes(2); + Assert.Equal(4d * 1024 * 1024 * 1024 * 1024 * 8, size.Bits); Assert.Equal(4d * 1024 * 1024 * 1024 * 1024, size.Bytes); - Assert.Equal(4d * 1024 * 1024 * 1024, size.Kilobytes); - Assert.Equal(4d * 1024 * 1024, size.Megabytes); - Assert.Equal(4d * 1024, size.Gigabytes); - Assert.Equal(4d, size.Terabytes); + Assert.Equal(4d * 1024 * 1024 * 1024, size.Kibibytes); + Assert.Equal(4d * 1024 * 1024, size.Mebibytes); + Assert.Equal(4d * 1024, size.Gibibytes); + Assert.Equal(4d, size.Tebibytes); } [Fact] diff --git a/src/Humanizer.Tests.Shared/Bytes/ByteRateTests.cs b/src/Humanizer.Tests.Shared/Bytes/ByteRateTests.cs index e596211a2..0a8edc453 100644 --- a/src/Humanizer.Tests.Shared/Bytes/ByteRateTests.cs +++ b/src/Humanizer.Tests.Shared/Bytes/ByteRateTests.cs @@ -10,11 +10,11 @@ public class ByteRateTests { [Theory] [InlineData(400, 1, "400 B/s")] - [InlineData(4 * 1024, 1, "4 KB/s")] - [InlineData(4 * 1024 * 1024, 1, "4 MB/s")] - [InlineData(4 * 2 * 1024 * 1024, 2, "4 MB/s")] - [InlineData(4 * 1024, 0.1, "40 KB/s")] - [InlineData(15 * 60 * 1024 * 1024, 60, "15 MB/s")] + [InlineData(4 * 1000, 1, "4 kB/s")] + [InlineData(4 * 1000 * 1000, 1, "4 MB/s")] + [InlineData(4 * 2 * 1000 * 1000, 2, "4 MB/s")] + [InlineData(4 * 1000, 0.1, "40 kB/s")] + [InlineData(15 * 60 * 1000 * 1000, 60, "15 MB/s")] public void HumanizesRates(long inputBytes, double perSeconds, string expectedValue) { var size = new ByteSize(inputBytes); @@ -32,9 +32,9 @@ public void HumanizesRates(long inputBytes, double perSeconds, string expectedVa [InlineData(10, 1, TimeUnit.Second, "10 MB/s")] [InlineData(10, 60, TimeUnit.Minute, "10 MB/min")] [InlineData(10, 60 * 60, TimeUnit.Hour, "10 MB/hour")] - [InlineData(1, 10 * 1, TimeUnit.Second, "102.4 KB/s")] - [InlineData(1, 10 * 60, TimeUnit.Minute, "102.4 KB/min")] - [InlineData(1, 10 * 60 * 60, TimeUnit.Hour, "102.4 KB/hour")] + [InlineData(1, 10 * 1, TimeUnit.Second, "100 kB/s")] + [InlineData(1, 10 * 60, TimeUnit.Minute, "100 kB/min")] + [InlineData(1, 10 * 60 * 60, TimeUnit.Hour, "100 kB/hour")] public void TimeUnitTests(long megabytes, double measurementIntervalSeconds, TimeUnit displayInterval, string expectedValue) { var size = ByteSize.FromMegabytes(megabytes); @@ -47,8 +47,8 @@ public void TimeUnitTests(long megabytes, double measurementIntervalSeconds, Tim } [Theory] - [InlineData(19854651984, 1, TimeUnit.Second, null, "18.4910856038332 GB/s")] - [InlineData(19854651984, 1, TimeUnit.Second, "#.##", "18.49 GB/s")] + [InlineData(19854651984, 1, TimeUnit.Second, null, "19.854651984 GB/s")] + [InlineData(19854651984, 1, TimeUnit.Second, "#.##", "19.85 GB/s")] public void FormattedTimeUnitTests(long bytes, int measurementIntervalSeconds, TimeUnit displayInterval, string format, string expectedValue) { var size = ByteSize.FromBytes(bytes); diff --git a/src/Humanizer.Tests.Shared/Bytes/ByteSizeExtensionsTests.cs b/src/Humanizer.Tests.Shared/Bytes/ByteSizeExtensionsTests.cs index dfa0e032c..b64ae7d09 100644 --- a/src/Humanizer.Tests.Shared/Bytes/ByteSizeExtensionsTests.cs +++ b/src/Humanizer.Tests.Shared/Bytes/ByteSizeExtensionsTests.cs @@ -7,6 +7,7 @@ namespace Humanizer.Tests.Bytes public class ByteSizeExtensionsTests { + #region SI-unit extensions (kilo, mega, giga, etc.) [Fact] public void ByteTerabytes() { @@ -65,7 +66,7 @@ public void LongTerabytes() [Theory] [InlineData(2, null, "2 TB")] - [InlineData(2, "GB", "2048 GB")] + [InlineData(2, "GB", "2000 GB")] [InlineData(2.123, "#.#", "2.1 TB")] public void HumanizesTerabytes(double input, string format, string expectedValue) { @@ -132,7 +133,7 @@ public void LongGigabytes() [InlineData(0, null, "0 b")] [InlineData(0, "GB", "0 GB")] [InlineData(2, null, "2 GB")] - [InlineData(2, "MB", "2048 MB")] + [InlineData(2, "MB", "2000 MB")] [InlineData(2.123, "#.##", "2.12 GB")] public void HumanizesGigabytes(double input, string format, string expectedValue) { @@ -199,7 +200,7 @@ public void LongMegabytes() [InlineData(0, null, "0 b")] [InlineData(0, "MB", "0 MB")] [InlineData(2, null, "2 MB")] - [InlineData(2, "KB", "2048 KB")] + [InlineData(2, "kB", "2000 kB")] [InlineData(2.123, "#", "2 MB")] public void HumanizesMegabytes(double input, string format, string expectedValue) { @@ -264,14 +265,241 @@ public void LongKilobytes() [Theory] [InlineData(0, null, "0 b")] - [InlineData(0, "KB", "0 KB")] - [InlineData(2, null, "2 KB")] - [InlineData(2, "B", "2048 B")] - [InlineData(2.123, "#.####", "2.123 KB")] + [InlineData(0, "kB", "0 kB")] + [InlineData(2, null, "2 kB")] + [InlineData(2, "B", "2000 B")] + [InlineData(2.123, "#.####", "2.123 kB")] public void HumanizesKilobytes(double input, string format, string expectedValue) { Assert.Equal(expectedValue, input.Kilobytes().Humanize(format)); } + #endregion + + #region IEC-unit extensions (kibi, mebi, gibi, etc.) + [Fact] + public void ByteTebibytes() + { + const byte size = 2; + Assert.Equal(ByteSize.FromTebibytes(size), size.Tebibytes()); + } + + [Fact] + public void SbyteTebibytes() + { + const sbyte size = 2; + Assert.Equal(ByteSize.FromTebibytes(size), size.Tebibytes()); + } + + [Fact] + public void ShortTebibytes() + { + const short size = 2; + Assert.Equal(ByteSize.FromTebibytes(size), size.Tebibytes()); + } + + [Fact] + public void UshortTebibytes() + { + const ushort size = 2; + Assert.Equal(ByteSize.FromTebibytes(size), size.Tebibytes()); + } + + [Fact] + public void IntTebibytes() + { + const int size = 2; + Assert.Equal(ByteSize.FromTebibytes(size), size.Tebibytes()); + } + + [Fact] + public void UintTebibytes() + { + const uint size = 2; + Assert.Equal(ByteSize.FromTebibytes(size), size.Tebibytes()); + } + + [Fact] + public void DoubleTebibytes() + { + const double size = 2; + Assert.Equal(ByteSize.FromTebibytes(size), size.Tebibytes()); + } + + [Fact] + public void LongTebibytes() + { + const long size = 2; + Assert.Equal(ByteSize.FromTebibytes(size), size.Tebibytes()); + } + + [Fact] + public void ByteGibibytes() + { + const byte size = 2; + Assert.Equal(ByteSize.FromGibibytes(size), size.Gibibytes()); + } + + [Fact] + public void SbyteGibibytes() + { + const sbyte size = 2; + Assert.Equal(ByteSize.FromGibibytes(size), size.Gibibytes()); + } + + [Fact] + public void ShortGibibytes() + { + const short size = 2; + Assert.Equal(ByteSize.FromGibibytes(size), size.Gibibytes()); + } + + [Fact] + public void UshortGibibytes() + { + const ushort size = 2; + Assert.Equal(ByteSize.FromGibibytes(size), size.Gibibytes()); + } + + [Fact] + public void IntGibibytes() + { + const int size = 2; + Assert.Equal(ByteSize.FromGibibytes(size), size.Gibibytes()); + } + + [Fact] + public void UintGibibytes() + { + const uint size = 2; + Assert.Equal(ByteSize.FromGibibytes(size), size.Gibibytes()); + } + + [Fact] + public void DoubleGibibytes() + { + const double size = 2; + Assert.Equal(ByteSize.FromGibibytes(size), size.Gibibytes()); + } + + [Fact] + public void LongGibibytes() + { + const long size = 2; + Assert.Equal(ByteSize.FromGibibytes(size), size.Gibibytes()); + } + + [Fact] + public void ByteMebibytes() + { + const byte size = 2; + Assert.Equal(ByteSize.FromMebibytes(size), size.Mebibytes()); + } + + [Fact] + public void SbyteMebibytes() + { + const sbyte size = 2; + Assert.Equal(ByteSize.FromMebibytes(size), size.Mebibytes()); + } + + [Fact] + public void ShortMebibytes() + { + const short size = 2; + Assert.Equal(ByteSize.FromMebibytes(size), size.Mebibytes()); + } + + [Fact] + public void UshortMebibytes() + { + const ushort size = 2; + Assert.Equal(ByteSize.FromMebibytes(size), size.Mebibytes()); + } + + [Fact] + public void IntMebibytes() + { + const int size = 2; + Assert.Equal(ByteSize.FromMebibytes(size), size.Mebibytes()); + } + + [Fact] + public void UintMebibytes() + { + const uint size = 2; + Assert.Equal(ByteSize.FromMebibytes(size), size.Mebibytes()); + } + + [Fact] + public void DoubleMebibytes() + { + const double size = 2; + Assert.Equal(ByteSize.FromMebibytes(size), size.Mebibytes()); + } + + [Fact] + public void LongMebibytes() + { + const long size = 2; + Assert.Equal(ByteSize.FromMebibytes(size), size.Mebibytes()); + } + + [Fact] + public void ByteKibibytes() + { + const byte size = 2; + Assert.Equal(ByteSize.FromKibibytes(size), size.Kibibytes()); + } + + [Fact] + public void SbyteKibibytes() + { + const sbyte size = 2; + Assert.Equal(ByteSize.FromKibibytes(size), size.Kibibytes()); + } + + [Fact] + public void ShortKibibytes() + { + const short size = 2; + Assert.Equal(ByteSize.FromKibibytes(size), size.Kibibytes()); + } + + [Fact] + public void UshortKibibytes() + { + const ushort size = 2; + Assert.Equal(ByteSize.FromKibibytes(size), size.Kibibytes()); + } + + [Fact] + public void IntKibibytes() + { + const int size = 2; + Assert.Equal(ByteSize.FromKibibytes(size), size.Kibibytes()); + } + + [Fact] + public void UintKibibytes() + { + const uint size = 2; + Assert.Equal(ByteSize.FromKibibytes(size), size.Kibibytes()); + } + + [Fact] + public void DoubleKibibytes() + { + const double size = 2; + Assert.Equal(ByteSize.FromKibibytes(size), size.Kibibytes()); + } + + [Fact] + public void LongKibibytes() + { + const long size = 2; + Assert.Equal(ByteSize.FromKibibytes(size), size.Kibibytes()); + } + #endregion [Fact] public void ByteBytes() @@ -334,11 +562,11 @@ public void LongBytes() [InlineData(0, "#.##", "0 b")] [InlineData(0, "B", "0 B")] [InlineData(2, null, "2 B")] - [InlineData(2000, "KB", "1.95 KB")] - [InlineData(2123, "#.##", "2.07 KB")] - [InlineData(10000000, "KB", "9765.63 KB")] - [InlineData(10000000, "#,##0 KB", "9,766 KB")] - [InlineData(10000000, "#,##0.# KB", "9,765.6 KB")] + [InlineData(2000, "kB", "2 kB")] + [InlineData(2123, "#.##", "2.12 kB")] + [InlineData(10000000, "kB", "10000 kB")] + [InlineData(10000000, "#,##0 kB", "10,000 kB")] + [InlineData(10000700, "#,##0.# kB", "10,000.7 kB")] public void HumanizesBytes(double input, string format, string expectedValue) { Assert.Equal(expectedValue, input.Bytes().Humanize(format)); @@ -398,7 +626,7 @@ public void LongBits() [InlineData(0, "b", "0 b")] [InlineData(2, null, "2 b")] [InlineData(12, "B", "1.5 B")] - [InlineData(10000, "#.# KB", "1.2 KB")] + [InlineData(10000, "#.# kB", "1.3 kB")] public void HumanizesBits(long input, string format, string expectedValue) { Assert.Equal(expectedValue, input.Bits().Humanize(format)); diff --git a/src/Humanizer.Tests.Shared/Bytes/ComparingTests.cs b/src/Humanizer.Tests.Shared/Bytes/ComparingTests.cs index 0f4caea39..adb7300dc 100644 --- a/src/Humanizer.Tests.Shared/Bytes/ComparingTests.cs +++ b/src/Humanizer.Tests.Shared/Bytes/ComparingTests.cs @@ -34,8 +34,9 @@ public void CompareUntyped(double value, double valueToCompareWith, int expected } [Theory] - [InlineData(new[] { "1GB", "3KB", "5MB" }, new[] { "3KB", "5MB", "1GB"})] - [InlineData(new[] { "1MB", "3KB", "5MB" }, new[] { "3KB", "1MB", "5MB"})] + [InlineData(new[] { "1GB", "3kB", "5MB" }, new[] { "3kB", "5MB", "1GB"})] + [InlineData(new[] { "1MB", "3kB", "5MB" }, new[] { "3kB", "1MB", "5MB"})] + [InlineData(new[] { "1MiB" , "1MB" }, new[] { "1MB", "1MiB" })] public void SortList(IEnumerable values, IEnumerable expected) { var list = values.Select(ByteSize.Parse).ToList(); diff --git a/src/Humanizer.Tests.Shared/Bytes/CreatingTests.cs b/src/Humanizer.Tests.Shared/Bytes/CreatingTests.cs index e14e7f6eb..c847c37f4 100644 --- a/src/Humanizer.Tests.Shared/Bytes/CreatingTests.cs +++ b/src/Humanizer.Tests.Shared/Bytes/CreatingTests.cs @@ -30,14 +30,20 @@ public class CreatingTests [Fact] public void Constructor() { - var result = new ByteSize(1099511627776); + var result = new ByteSize(1e12); - Assert.Equal(8.796093022208e12, result.Bits); - Assert.Equal(1099511627776, result.Bytes); - Assert.Equal(1073741824, result.Kilobytes); - Assert.Equal(1048576, result.Megabytes); - Assert.Equal(1024, result.Gigabytes); - Assert.Equal(1, result.Terabytes); + Assert.Equal(8e12, result.Bits,3); + Assert.Equal(1e12, result.Bytes,3); + + Assert.Equal(1e9, result.Kilobytes,3); + Assert.Equal(1e6, result.Megabytes,3); + Assert.Equal(1000, result.Gigabytes,3); + Assert.Equal(1, result.Terabytes,3); + + Assert.Equal(976562500.0, result.Kibibytes, 3); + Assert.Equal(953674.316, result.Mebibytes, 3); + Assert.Equal(931.323, result.Gibibytes,3); + Assert.Equal(0.909, result.Tebibytes,3); } [Fact] @@ -46,7 +52,7 @@ public void FromBits() var result = ByteSize.FromBits(8); Assert.Equal(8, result.Bits); - Assert.Equal(1, result.Bytes); + Assert.Equal(1, result.Bytes,3); } [Fact] @@ -55,7 +61,7 @@ public void FromBytes() var result = ByteSize.FromBytes(1.5); Assert.Equal(12, result.Bits); - Assert.Equal(1.5, result.Bytes); + Assert.Equal(1.5, result.Bytes, 3); } [Fact] @@ -63,8 +69,17 @@ public void FromKilobytes() { var result = ByteSize.FromKilobytes(1.5); - Assert.Equal(1536, result.Bytes); - Assert.Equal(1.5, result.Kilobytes); + Assert.Equal(1500, result.Bytes, 3); + Assert.Equal(1.5, result.Kilobytes, 3); + } + [Fact] + public void FromKibibytes() + { + var result = ByteSize.FromKibibytes(1.5); + + Assert.Equal(1536, result.Bytes, 3); + Assert.Equal(1.5, result.Kibibytes, 3); + Assert.Equal(1.536, result.Kilobytes, 3); } [Fact] @@ -72,8 +87,17 @@ public void FromMegabytes() { var result = ByteSize.FromMegabytes(1.5); - Assert.Equal(1572864, result.Bytes); - Assert.Equal(1.5, result.Megabytes); + Assert.Equal(1500000, result.Bytes, 3); + Assert.Equal(1.5, result.Megabytes, 3); + } + [Fact] + public void FromMebibytes() + { + var result = ByteSize.FromMebibytes(1.5); + + Assert.Equal(1572864, result.Bytes, 3); + Assert.Equal(1.5, result.Mebibytes, 3); + Assert.Equal(1.573, result.Megabytes, 3); } [Fact] @@ -81,8 +105,18 @@ public void FromGigabytes() { var result = ByteSize.FromGigabytes(1.5); - Assert.Equal(1610612736, result.Bytes); - Assert.Equal(1.5, result.Gigabytes); + Assert.Equal(1500000000, result.Bytes, 3); + Assert.Equal(1.5, result.Gigabytes, 3); + } + + [Fact] + public void FromGibibytes() + { + var result = ByteSize.FromGibibytes(1.5); + + Assert.Equal(1610612736, result.Bytes, 3); + Assert.Equal(1.5, result.Gibibytes, 3); + Assert.Equal(1.611, result.Gigabytes, 3); } [Fact] @@ -90,8 +124,17 @@ public void FromTerabytes() { var result = ByteSize.FromTerabytes(1.5); - Assert.Equal(1649267441664, result.Bytes); - Assert.Equal(1.5, result.Terabytes); + Assert.Equal(1500000000000, result.Bytes, 3); + Assert.Equal(1.5, result.Terabytes, 3); + } + [Fact] + public void FromTebibytes() + { + var result = ByteSize.FromTebibytes(1.5); + + Assert.Equal(1649267441664, result.Bytes, 3); + Assert.Equal(1.5, result.Tebibytes, 3); + Assert.Equal(1.649, result.Terabytes, 3); } } } diff --git a/src/Humanizer.Tests.Shared/Bytes/ParsingTests.cs b/src/Humanizer.Tests.Shared/Bytes/ParsingTests.cs index 07cdf8338..31b972f7e 100644 --- a/src/Humanizer.Tests.Shared/Bytes/ParsingTests.cs +++ b/src/Humanizer.Tests.Shared/Bytes/ParsingTests.cs @@ -34,19 +34,37 @@ public class ParsingTests [Fact] public void Parse() { - Assert.Equal(ByteSize.FromKilobytes(1020), ByteSize.Parse("1020KB")); + Assert.Equal(ByteSize.FromKilobytes(1020), ByteSize.Parse("1020kB")); + Assert.Equal(ByteSize.FromKibibytes(1020), ByteSize.Parse("1020KiB")); } - - [Fact] - public void TryParse() + [Theory] + [InlineData("1020kB")] + [InlineData("1020 kB")] + [InlineData("1020 KB")] + [InlineData("1020 kB ")] + [InlineData(" 1020 kB")] + public void TryParseKilobytes(string rawValue) { - ByteSize resultByteSize; - var resultBool = ByteSize.TryParse("1020KB", out resultByteSize); + var parseSuccess = ByteSize.TryParse(rawValue, out ByteSize resultByteSize); - Assert.True(resultBool); + Assert.True(parseSuccess); Assert.Equal(ByteSize.FromKilobytes(1020), resultByteSize); } + [Theory] + [InlineData("1020kiB")] + [InlineData("1020 kiB")] + [InlineData("1020 KiB")] + [InlineData("1020 kiB ")] + [InlineData(" 1020 kiB")] + public void TryParseKibibytes(string rawValue) + { + var parseSuccess = ByteSize.TryParse(rawValue, out ByteSize resultByteSize); + + Assert.True(parseSuccess); + Assert.Equal(ByteSize.FromKibibytes(1020), resultByteSize); + } + [Fact] public void ParseDecimalMegabytes() { @@ -56,7 +74,9 @@ public void ParseDecimalMegabytes() [Theory] [InlineData("Unexpected Value")] [InlineData("1000")] - [InlineData("KB")] + [InlineData("kb")] + [InlineData("10-0kb")] + [InlineData("10-kb")] public void TryParseReturnsFalseOnBadValue(string input) { ByteSize resultByteSize; @@ -69,7 +89,7 @@ public void TryParseReturnsFalseOnBadValue(string input) [Fact] public void TryParseWorksWithLotsOfSpaces() { - Assert.Equal(ByteSize.FromKilobytes(100), ByteSize.Parse(" 100 KB ")); + Assert.Equal(ByteSize.FromKilobytes(100), ByteSize.Parse(" 100 kB ")); } [Fact] @@ -87,7 +107,13 @@ public void ParseThrowsOnInvalid() [Fact] public void ParseThrowsOnNull() { - Assert.Throws(() => { ByteSize.Parse(null); }); + Assert.Throws(() => { ByteSize.Parse(null); }); + } + + [Fact] + public void ParseThrowsOnEmptyString() + { + Assert.Throws(() => { ByteSize.Parse(""); }); } [Fact] @@ -105,7 +131,13 @@ public void ParseBytes() [Fact] public void ParseKilobytes() { - Assert.Equal(ByteSize.FromKilobytes(1020), ByteSize.Parse("1020KB")); + Assert.Equal(ByteSize.FromKilobytes(1020), ByteSize.Parse("1020kB")); + } + + [Fact] + public void ParseKibibytes() + { + Assert.Equal(ByteSize.FromKibibytes(1020), ByteSize.Parse("1020kiB")); } [Fact] @@ -113,6 +145,12 @@ public void ParseMegabytes() { Assert.Equal(ByteSize.FromMegabytes(1000), ByteSize.Parse("1000MB")); } + + [Fact] + public void ParseMebibytes() + { + Assert.Equal(ByteSize.FromMebibytes(1000), ByteSize.Parse("1000MiB")); + } [Fact] public void ParseGigabytes() @@ -120,10 +158,31 @@ public void ParseGigabytes() Assert.Equal(ByteSize.FromGigabytes(805), ByteSize.Parse("805GB")); } + [Fact] + public void ParseGibibytes() + { + Assert.Equal(ByteSize.FromGibibytes(805), ByteSize.Parse("805GiB")); + } + [Fact] public void ParseTerabytes() { Assert.Equal(ByteSize.FromTerabytes(100), ByteSize.Parse("100TB")); } + + [Fact] + public void ParseTebibytes() + { + Assert.Equal(ByteSize.FromTebibytes(100), ByteSize.Parse("100TiB")); + } + + + [Theory] + [InlineData(-1024, "-1KB")] + [InlineData(-1000, "-1KiB")] + public void ParseNegativeBytes(int expectedBytes, string bytesToParse) + { + Assert.Equal(ByteSize.FromBytes(expectedBytes), ByteSize.Parse(bytesToParse)); + } } } diff --git a/src/Humanizer.Tests.Shared/Bytes/ToStringTests.cs b/src/Humanizer.Tests.Shared/Bytes/ToStringTests.cs index b22faa657..324bda1eb 100644 --- a/src/Humanizer.Tests.Shared/Bytes/ToStringTests.cs +++ b/src/Humanizer.Tests.Shared/Bytes/ToStringTests.cs @@ -31,19 +31,19 @@ public class ToStringTests [Fact] public void ReturnsLargestMetricSuffix() { - Assert.Equal("10.5 KB", ByteSize.FromKilobytes(10.5).ToString()); + Assert.Equal("10.5 kB", ByteSize.FromKilobytes(10.5).ToString()); } [Fact] public void ReturnsDefaultNumberFormat() { - Assert.Equal("10.5 KB", ByteSize.FromKilobytes(10.5).ToString("KB")); + Assert.Equal("10.5 kB", ByteSize.FromKilobytes(10.5).ToString("kB")); } [Fact] public void ReturnsProvidedNumberFormat() { - Assert.Equal("10.1234 KB", ByteSize.FromKilobytes(10.1234).ToString("#.#### KB")); + Assert.Equal("10.1234 kB", ByteSize.FromKilobytes(10.1234).ToString("#.#### kB")); } [Fact] @@ -61,7 +61,7 @@ public void ReturnsBytes() [Fact] public void ReturnsKilobytes() { - Assert.Equal("10 KB", ByteSize.FromKilobytes(10).ToString("##.#### KB")); + Assert.Equal("10 kB", ByteSize.FromKilobytes(10).ToString("##.#### kB")); } [Fact] @@ -91,13 +91,13 @@ public void ReturnsSelectedFormat() [Fact] public void ReturnsLargestMetricPrefixLargerThanZero() { - Assert.Equal("512 KB", ByteSize.FromMegabytes(.5).ToString("#.#")); + Assert.Equal("500 kB", ByteSize.FromMegabytes(.5).ToString("#.#")); } [Fact] public void ReturnsLargestMetricPrefixLargerThanZeroForNegativeValues() { - Assert.Equal("-512 KB", ByteSize.FromMegabytes(-.5).ToString("#.#")); + Assert.Equal("-500 kB", ByteSize.FromMegabytes(-.5).ToString("#.#")); } } } diff --git a/src/Humanizer/Bytes/ByteSize.cs b/src/Humanizer/Bytes/ByteSize.cs index c13f55a37..3987238fe 100644 --- a/src/Humanizer/Bytes/ByteSize.cs +++ b/src/Humanizer/Bytes/ByteSize.cs @@ -21,6 +21,9 @@ //THE SOFTWARE. using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; namespace Humanizer.Bytes { @@ -34,24 +37,63 @@ public struct ByteSize : IComparable, IEquatable, IComparabl public static readonly ByteSize MaxValue = FromBits(long.MaxValue); public const long BitsInByte = 8; - public const long BytesInKilobyte = 1024; - public const long BytesInMegabyte = 1048576; - public const long BytesInGigabyte = 1073741824; - public const long BytesInTerabyte = 1099511627776; + public static readonly long BytesInKilobyte = Base10Pow(3); + public static readonly long BytesInMegabyte = Base10Pow(6); + public static readonly long BytesInGigabyte = Base10Pow(9); + public static readonly long BytesInTerabyte = Base10Pow(12); + + public static readonly long BytesInKibibyte = Base2Pow(10); + public static readonly long BytesInMebibyte = Base2Pow(20); + public static readonly long BytesInGibibyte = Base2Pow(30); + public static readonly long BytesInTebibyte = Base2Pow(40); public const string BitSymbol = "b"; public const string ByteSymbol = "B"; - public const string KilobyteSymbol = "KB"; + + public const string KilobyteSymbol = "kB"; public const string MegabyteSymbol = "MB"; public const string GigabyteSymbol = "GB"; public const string TerabyteSymbol = "TB"; - public long Bits { get; private set; } - public double Bytes { get; private set; } - public double Kilobytes { get; private set; } - public double Megabytes { get; private set; } - public double Gigabytes { get; private set; } - public double Terabytes { get; private set; } + public const string KibibyteSymbol = "KiB"; + public const string MebibyteSymbol = "MiB"; + public const string GibibyteSymbol = "GiB"; + public const string TebibyteSymbol = "TiB"; + + private static readonly Dictionary UppercasePrefixToMultiplicatorMap = new Dictionary + { + {"", 1 }, + + {"K", BytesInKilobyte }, + {"M", BytesInMegabyte }, + {"G", BytesInGigabyte }, + {"T", BytesInTerabyte }, + + {"KI", BytesInKibibyte }, + {"MI", BytesInMebibyte }, + {"GI", BytesInGibibyte }, + {"TI", BytesInTebibyte }, + }; + private static long Base10Pow(int exponent) + { + return (long)Math.Pow(10, exponent); + } + private static long Base2Pow(int exponent) + { + return (long)Math.Pow(2, exponent); + } + + public long Bits { get; } + public double Bytes { get; } + public double Kilobytes { get; } + public double Megabytes { get; } + public double Gigabytes { get; } + public double Terabytes { get; } + + public double Kibibytes { get; } + public double Mebibytes { get; } + public double Gibibytes { get; } + public double Tebibytes { get; } public string LargestWholeNumberSymbol { @@ -111,6 +153,10 @@ public ByteSize(double byteSize) Megabytes = byteSize / BytesInMegabyte; Gigabytes = byteSize / BytesInGigabyte; Terabytes = byteSize / BytesInTerabyte; + Kibibytes = byteSize/BytesInKibibyte; + Mebibytes = byteSize / BytesInMebibyte; + Gibibytes = byteSize / BytesInGibibyte; + Tebibytes = byteSize / BytesInTebibyte; } public static ByteSize FromBits(long value) @@ -142,6 +188,25 @@ public static ByteSize FromTerabytes(double value) { return new ByteSize(value * BytesInTerabyte); } + public static ByteSize FromKibibytes(double value) + { + return new ByteSize(value * BytesInKibibyte); + } + + public static ByteSize FromMebibytes(double value) + { + return new ByteSize(value * BytesInMebibyte); + } + + public static ByteSize FromGibibytes(double value) + { + return new ByteSize(value * BytesInGibibyte); + } + + public static ByteSize FromTebibytes(double value) + { + return new ByteSize(value * BytesInTebibyte); + } /// /// Converts the value of the current ByteSize object to a string. @@ -151,7 +216,7 @@ public static ByteSize FromTerabytes(double value) /// public override string ToString() { - return string.Format("{0} {1}", LargestWholeNumberValue, LargestWholeNumberSymbol); + return $"{LargestWholeNumberValue} {LargestWholeNumberSymbol}"; } public string ToString(string format) @@ -159,23 +224,31 @@ public string ToString(string format) if (!format.Contains("#") && !format.Contains("0")) format = "0.## " + format; - Func has = s => format.IndexOf(s, StringComparison.CurrentCultureIgnoreCase) != -1; + Func has = s => format.IndexOf(s, StringComparison.CurrentCultureIgnoreCase) >= 0; Func output = n => n.ToString(format); if (has(TerabyteSymbol)) return output(Terabytes); + if (has(TebibyteSymbol)) + return output(Tebibytes); if (has(GigabyteSymbol)) return output(Gigabytes); + if (has(GibibyteSymbol)) + return output(Gibibytes); if (has(MegabyteSymbol)) return output(Megabytes); + if (has(MebibyteSymbol)) + return output(Mebibytes); if (has(KilobyteSymbol)) return output(Kilobytes); + if (has(KibibyteSymbol)) + return output(Kibibytes); // Byte and Bit symbol look must be case-sensitive - if (format.IndexOf(ByteSymbol, StringComparison.Ordinal) != -1) + if (format.IndexOf(ByteSymbol, StringComparison.Ordinal) >= 0) return output(Bytes); - if (format.IndexOf(BitSymbol, StringComparison.Ordinal) != -1) + if (format.IndexOf(BitSymbol, StringComparison.Ordinal) >= 0) return output(Bits); var formattedLargeWholeNumberValue = LargestWholeNumberValue.ToString(format); @@ -184,7 +257,7 @@ public string ToString(string format) ? "0" : formattedLargeWholeNumberValue; - return string.Format("{0} {1}", formattedLargeWholeNumberValue, LargestWholeNumberSymbol); + return $"{formattedLargeWholeNumberValue} {LargestWholeNumberSymbol}"; } public override bool Equals(object value) @@ -247,21 +320,41 @@ public ByteSize AddKilobytes(double value) return this + FromKilobytes(value); } + public ByteSize AddKibibytes(double value) + { + return this + FromKibibytes(value); + } + public ByteSize AddMegabytes(double value) { return this + FromMegabytes(value); } + public ByteSize AddMebibytes(double value) + { + return this + FromMebibytes(value); + } + public ByteSize AddGigabytes(double value) { return this + FromGigabytes(value); } + public ByteSize AddGibibytes(double value) + { + return this + FromGibibytes(value); + } + public ByteSize AddTerabytes(double value) { return this + FromTerabytes(value); } + public ByteSize AddTebibytes(double value) + { + return this + FromTebibytes(value); + } + public ByteSize Subtract(ByteSize bs) { return new ByteSize(Bytes - bs.Bytes); @@ -316,82 +409,78 @@ public ByteSize Subtract(ByteSize bs) { return b1.Bits >= b2.Bits; } - + public static bool TryParse(string s, out ByteSize result) { - // Arg checking - if (string.IsNullOrWhiteSpace(s)) - throw new ArgumentNullException(nameof(s), "String is null or whitespace"); - - // Setup the result result = new ByteSize(); - // Get the index of the first non-digit character - s = s.TrimStart(); // Protect against leading spaces + if (string.IsNullOrWhiteSpace(s)) + return false; - int num; - var found = false; + s = s.TrimStart(); + var firstNonDigit = FindIndexOfFirstNonDigit(s); + if (firstNonDigit < 0) + { + // If no unit is specified, we don't know how to parse the value + // Although it would be reasonable to assume bytes + return false; + } - // Acquiring culture specific decimal separator - var decSep = Convert.ToChar(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); - - // Pick first non-digit number - for (num = 0; num < s.Length; num++) - if (!(char.IsDigit(s[num]) || s[num] == decSep)) - { - found = true; - break; - } + var rawNumber = s.Substring(0, firstNonDigit); + var rawUnit = s.Substring(firstNonDigit, s.Length - firstNonDigit).Trim(); - if (found == false) + double value; + if (!double.TryParse(rawNumber, out value)) return false; - var lastNumber = num; + var prefixAndUnit = SplitToPrefixAndUnit(rawUnit); - // Cut the input string in half - var numberPart = s.Substring(0, lastNumber).Trim(); - var sizePart = s.Substring(lastNumber, s.Length - lastNumber).Trim(); + if (prefixAndUnit == null) + return false; // unable to determine unit or prefix - // Get the numeric part - double number; - if (!double.TryParse(numberPart, out number)) + var isByte = prefixAndUnit.Item2 == ByteSymbol; + var uppercasePrefix = prefixAndUnit.Item1.ToUpperInvariant(); + + // No fractional bits + if (!isByte && value % 1 != 0) + return false; + + long prefixMultiplicator; + if (!UppercasePrefixToMultiplicatorMap + .TryGetValue(uppercasePrefix, out prefixMultiplicator)) return false; - // Get the magnitude part - switch (sizePart.ToUpper()) + result = isByte + ? FromBytes(prefixMultiplicator * value) + : FromBits(prefixMultiplicator * (long)value); + + return true; + } + + private static Tuple SplitToPrefixAndUnit(string unit) + { + if (unit.EndsWith(BitSymbol)) + return Tuple.Create(unit.Substring(0, unit.Length - 1).Trim(), BitSymbol); + if (unit.EndsWith(ByteSymbol)) + return Tuple.Create(unit.Substring(0, unit.Length - 1).Trim(), ByteSymbol); + + return null; + } + + private static int FindIndexOfFirstNonDigit(string s) + { + // Make the assumption that the decimal separator is always exactly one char explicit + Debug.Assert(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator.Length == 1); + + var decimalSeparator = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); + + for (var index = 0; index < s.Length; index++) { - case ByteSymbol: - if (sizePart == BitSymbol) - { // Bits - if (number % 1 != 0) // Can't have partial bits - return false; - - result = FromBits((long)number); - } - else - { // Bytes - result = FromBytes(number); - } - break; - - case KilobyteSymbol: - result = FromKilobytes(number); - break; - - case MegabyteSymbol: - result = FromMegabytes(number); - break; - - case GigabyteSymbol: - result = FromGigabytes(number); - break; - - case TerabyteSymbol: - result = FromTerabytes(number); - break; + if (!(char.IsDigit(s[index]) || s[index] == decimalSeparator || (index == 0 && s[index] == '-'))) + return index; } - return true; + return -1; } public static ByteSize Parse(string s) diff --git a/src/Humanizer/Bytes/ByteSizeExtensions.cs b/src/Humanizer/Bytes/ByteSizeExtensions.cs index b16ad5992..ba5e674d5 100644 --- a/src/Humanizer/Bytes/ByteSizeExtensions.cs +++ b/src/Humanizer/Bytes/ByteSizeExtensions.cs @@ -159,6 +159,7 @@ public static ByteSize Bytes(this long input) return ByteSize.FromBytes(input); } + #region Si-unit extensions (kilo, Mega, Giga, etc.) /// /// Considers input as kilobytes /// @@ -478,6 +479,329 @@ public static ByteSize Terabytes(this long input) { return ByteSize.FromTerabytes(input); } + #endregion + + #region IEC-unit extensions (kibi, Mebi, Gibi, etc.) + /// + /// Considers input as Kibibytes + /// + /// + /// + public static ByteSize Kibibytes(this byte input) + { + return ByteSize.FromKibibytes(input); + } + + /// + /// Considers input as Kibibytes + /// + /// + /// + public static ByteSize Kibibytes(this sbyte input) + { + return ByteSize.FromKibibytes(input); + } + + /// + /// Considers input as Kibibytes + /// + /// + /// + public static ByteSize Kibibytes(this short input) + { + return ByteSize.FromKibibytes(input); + } + + /// + /// Considers input as Kibibytes + /// + /// + /// + public static ByteSize Kibibytes(this ushort input) + { + return ByteSize.FromKibibytes(input); + } + + /// + /// Considers input as Kibibytes + /// + /// + /// + public static ByteSize Kibibytes(this int input) + { + return ByteSize.FromKibibytes(input); + } + + /// + /// Considers input as Kibibytes + /// + /// + /// + public static ByteSize Kibibytes(this uint input) + { + return ByteSize.FromKibibytes(input); + } + + /// + /// Considers input as Kibibytes + /// + /// + /// + public static ByteSize Kibibytes(this double input) + { + return ByteSize.FromKibibytes(input); + } + + /// + /// Considers input as Kibibytes + /// + /// + /// + public static ByteSize Kibibytes(this long input) + { + return ByteSize.FromKibibytes(input); + } + + /// + /// Considers input as Mebibytes + /// + /// + /// + public static ByteSize Mebibytes(this byte input) + { + return ByteSize.FromMebibytes(input); + } + + /// + /// Considers input as Mebibytes + /// + /// + /// + public static ByteSize Mebibytes(this sbyte input) + { + return ByteSize.FromMebibytes(input); + } + + /// + /// Considers input as Mebibytes + /// + /// + /// + public static ByteSize Mebibytes(this short input) + { + return ByteSize.FromMebibytes(input); + } + + /// + /// Considers input as Mebibytes + /// + /// + /// + public static ByteSize Mebibytes(this ushort input) + { + return ByteSize.FromMebibytes(input); + } + + /// + /// Considers input as Mebibytes + /// + /// + /// + public static ByteSize Mebibytes(this int input) + { + return ByteSize.FromMebibytes(input); + } + + /// + /// Considers input as Mebibytes + /// + /// + /// + public static ByteSize Mebibytes(this uint input) + { + return ByteSize.FromMebibytes(input); + } + + /// + /// Considers input as Mebibytes + /// + /// + /// + public static ByteSize Mebibytes(this double input) + { + return ByteSize.FromMebibytes(input); + } + + /// + /// Considers input as Mebibytes + /// + /// + /// + public static ByteSize Mebibytes(this long input) + { + return ByteSize.FromMebibytes(input); + } + + /// + /// Considers input as Gibibytes + /// + /// + /// + public static ByteSize Gibibytes(this byte input) + { + return ByteSize.FromGibibytes(input); + } + + /// + /// Considers input as Gibibytes + /// + /// + /// + public static ByteSize Gibibytes(this sbyte input) + { + return ByteSize.FromGibibytes(input); + } + + /// + /// Considers input as Gibibytes + /// + /// + /// + public static ByteSize Gibibytes(this short input) + { + return ByteSize.FromGibibytes(input); + } + + /// + /// Considers input as Gibibytes + /// + /// + /// + public static ByteSize Gibibytes(this ushort input) + { + return ByteSize.FromGibibytes(input); + } + + /// + /// Considers input as Gibibytes + /// + /// + /// + public static ByteSize Gibibytes(this int input) + { + return ByteSize.FromGibibytes(input); + } + + /// + /// Considers input as Gibibytes + /// + /// + /// + public static ByteSize Gibibytes(this uint input) + { + return ByteSize.FromGibibytes(input); + } + + /// + /// Considers input as Gibibytes + /// + /// + /// + public static ByteSize Gibibytes(this double input) + { + return ByteSize.FromGibibytes(input); + } + + /// + /// Considers input as Gibibytes + /// + /// + /// + public static ByteSize Gibibytes(this long input) + { + return ByteSize.FromGibibytes(input); + } + + /// + /// Considers input as Tebibytes + /// + /// + /// + public static ByteSize Tebibytes(this byte input) + { + return ByteSize.FromTebibytes(input); + } + + /// + /// Considers input as Tebibytes + /// + /// + /// + public static ByteSize Tebibytes(this sbyte input) + { + return ByteSize.FromTebibytes(input); + } + + /// + /// Considers input as Tebibytes + /// + /// + /// + public static ByteSize Tebibytes(this short input) + { + return ByteSize.FromTebibytes(input); + } + + /// + /// Considers input as Tebibytes + /// + /// + /// + public static ByteSize Tebibytes(this ushort input) + { + return ByteSize.FromTebibytes(input); + } + + /// + /// Considers input as Tebibytes + /// + /// + /// + public static ByteSize Tebibytes(this int input) + { + return ByteSize.FromTebibytes(input); + } + + /// + /// Considers input as Tebibytes + /// + /// + /// + public static ByteSize Tebibytes(this uint input) + { + return ByteSize.FromTebibytes(input); + } + + /// + /// Considers input as Tebibytes + /// + /// + /// + public static ByteSize Tebibytes(this double input) + { + return ByteSize.FromTebibytes(input); + } + + /// + /// Considers input as Tebibytes + /// + /// + /// + public static ByteSize Tebibytes(this long input) + { + return ByteSize.FromTebibytes(input); + } + #endregion /// /// Turns a byte quantity into human readable form, eg 2 GB