Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support binary and decimal bytes (i.e. 1 kibi = 1024 b and 1 kb = 1000 b) #24

Merged
merged 28 commits into from
Jan 4, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2449aa0
Migrate from project.json to .csproj
omar Dec 2, 2018
a31720c
Metric byte POC
omar Dec 1, 2016
699457f
Add binary byte design doc
omar Dec 3, 2018
16b3f5d
Create BinaryByteSize class
omar Dec 6, 2018
fc5ad2e
Add tests
omar Dec 6, 2018
a39c68a
Update design document
omar Dec 21, 2018
78f9a92
Fix build
omar Dec 21, 2018
cc41b1e
Fix build
omar Dec 21, 2018
c131413
Remove net461 target framework
omar Dec 21, 2018
9aa161f
Add DecimalByteSize
omar Dec 23, 2018
6b778bc
Rename namespace to `ByteSize` and add tests
omar Dec 24, 2018
b0d990a
Revert min value, update release notes
omar Dec 24, 2018
db3cacc
Fix NuGet package properties
omar Dec 24, 2018
669a456
Fix typo
omar Dec 25, 2018
224e275
Combine Decimal and Binary ByteSize into a single struct
omar Mar 24, 2019
0b520c3
Fix tests
omar Mar 24, 2019
45bf4f9
Fix build
omar Mar 24, 2019
e025995
Merge commit 'd20566c66b1549abd478225553fb1ef0901b272d' into metric-byte
omar Mar 24, 2019
223afd0
Prepare alpha2
omar Mar 28, 2019
ca4ef02
Update copyright dates
omar Mar 28, 2019
6425b77
Fix #37 by avoiding division of a long/int64
omar Jun 5, 2019
521a761
Change name of largest number properties
omar Jun 5, 2019
c475563
Update docs and make docker build rerunnable
omar Jun 5, 2019
60a37b5
Update build and release notes
omar Jun 16, 2019
4b0a046
Update release notes
omar Jun 16, 2019
fe8d560
Add ToBinaryString method
omar Jul 14, 2019
e52aa36
Update readme
omar Jul 14, 2019
1721c66
Merge branch 'master' into metric-byte
omar Jan 4, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ src/packages/**
*.ncrunch*
*.ndproj
*.nupkg
**/project.lock.json
src/ByteSize/pack
29 changes: 5 additions & 24 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
dist: trusty
sudo: required

env:
global:
- DOTNET_CLI_TELEMETRY_OPTOUT=1
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true

before_install:
- sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
- echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list
- sudo apt-get update
- sudo apt-get install mono-complete
- sudo apt-get install referenceassemblies-pcl
- mono --version
- sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
- sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893
- sudo apt-get update
- sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177
- export PATH=/usr/local/share/dotnet/:$PATH
- dotnet --version

language: csharp
mono: none
dotnet: 2.1.500
script:
- make test

- make test
- make pack
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2013-2017 Omar Khudeira (http://omar.io)
Copyright (c) 2013-2018 Omar Khudeira (http://omar.io)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
12 changes: 4 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
build:
dotnet restore
dotnet build src/ByteSizeLib
dotnet build src

test:
dotnet restore
dotnet build src/ByteSizeLib
dotnet test src/ByteSizeLib.Tests
dotnet test src/ByteSize.Tests

package:
dotnet restore
dotnet pack src/ByteSizeLib -c Release -o pack
pack:
dotnet pack src/ByteSize -c Release -o pack
150 changes: 82 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,64 @@
# ByteSize

`ByteSize` is a utility class that makes byte size representation in code easier by removing ambiguity of the value being represented.
`ByteSize` is a utility class that makes byte size representation in code easier
by removing ambiguity of the value being represented.

`ByteSize` is to bytes what `System.TimeSpan` is to time.

[![](https://travis-ci.org/omar/ByteSize.svg?branch=master)](https://travis-ci.org/omar/ByteSize)
[![Stable nuget](https://img.shields.io/nuget/v/ByteSize.svg)](https://www.nuget.org/packages/ByteSize/)
[![](https://travis-ci.org/omar/DecimalByteSize.svg?branch=master)](https://travis-ci.org/omar/ByteSize)
[![Stable nuget](https://img.shields.io/nuget/v/DecimalByteSize.svg)](https://www.nuget.org/packages/ByteSize/)

#### Building
#### Development

* Windows: use Visual Studio
* Mac OS X
* Install [Mono](http://www.mono-project.com/download/).
* NOTE: using `brew install mono` will not install the PCL libraries required to build the PCL compatible DLLs. The PCL libraries can be installed by running the installer downloaded from http://www.mono-project.com/download/.
* Run `make build` in terminal.
* Linux
* Install [Mono](http://www.mono-project.com/docs/getting-started/install/linux/) and the reference assemblies (`sudo apt-get referenceassemblies-pcl`).
* Run `make build` in terminal.
- Install [.NET Core SDK](https://dotnet.microsoft.com/download)
- Build: `make build`
- Test: `make test`

## Usage
## Usage

`ByteSize` assumes `1 kilobyte` = `1024 bytes`. See [why here](http://omar.io/2017/01/16/when-technically-right-is-wrong-kilobytes.html).
`ByteSize` comes with 3 different ways to represent bytes:

- `DecimalByteSize` which assumes `1 kilobyte` = `1000 bytes` with 2 letter abbrevations `b`, `B`,`KB`, `MB`, `GB`, `TB`, `PB`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've started to use your alpha-version and faced with problem with naming KB for Decimal. It seems that for Decimal it should be 'kB' instead of 'KB'.
https://en.wikipedia.org/wiki/Kilobyte
https://en.wikipedia.org/wiki/Mega-

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be cool if you'll build version new alpha version before completing PR 😎

- `BinaryByteSize` which assumes `1 kibibyte` = `1024 bytes` with 3 letter abbrevations `b`, `B`,`KiB`, `MiB`, `GiB`, `TiB`, `PiB`.
- `NonStandardByteSize` which assumes `1 kilobyte` = `1024 bytes` with 2 letter abbrevations `b`, `B`,`KB`, `MiB`, `GB`, `TB`, `PB`.

The first two adhere to the IEC standard, see this [Wikipedia article](https://en.wikipedia.org/wiki/Kilobyte#Definitions_and_usage).


### Example

Without `ByteSize`:

```c#
static double MaxFileSizeMBs = 1.5;

// I need it in KBs!
var kilobytes = MaxFileSizeMBs * 1024; // 1536
var kilobytes = MaxFileSizeMBs * 1000; // 1500
```

With `ByteSize`:

```c#
static MaxFileSize = ByteSize.FromMegaBytes(1.5);
static MaxFileSize = DecimalByteSize.FromMegaBytes(1.5);

// I have it in KBs!
MaxFileSize.KiloBytes; // 1536
MaxFileSize.KiloBytes; // 1500
```

`ByeSize` behaves like any other struct backed by a numerical value.

```c#
// Add
var monthlyUsage = ByteSize.FromGigaBytes(10);
var currentUsage = ByteSize.FromMegaBytes(512);
var monthlyUsage = DecimalByteSize.FromGigaBytes(10);
var currentUsage = DecimalByteSize.FromMegaBytes(512);
ByteSize total = monthlyUsage + currentUsage;

total.Add(ByteSize.FromKiloBytes(10));
total.Add(DecimalByteSize.FromKiloBytes(10));
total.AddGigaBytes(10);

// Subtract
var delta = total.Subtract(ByteSize.FromKiloBytes(10));
delta = delta - ByteSize.FromGigaBytes(100);
var delta = total.Subtract(DecimalByteSize.FromKiloBytes(10));
delta = delta - DecimalByteSize.FromGigaBytes(100);
delta = delta.AddMegaBytes(-100);
```

Expand All @@ -62,39 +67,39 @@ delta = delta.AddMegaBytes(-100);
You can create a `ByteSize` object from `bits`, `bytes`, `kilobytes`, `megabytes`, `gigabytes`, and `terabytes`.

```c#
new ByteSize(1.5); // Constructor takes in bytes
new DecimalByteSize(1.5); // Constructor takes in bytes

// Static Constructors
ByteSize.FromBits(10); // Bits are whole numbers only
ByteSize.FromBytes(1.5); // Same as constructor
ByteSize.FromKiloBytes(1.5);
ByteSize.FromMegaBytes(1.5);
ByteSize.FromGigaBytes(1.5);
ByteSize.FromTeraBytes(1.5);
DecimalByteSize.FromBits(10); // Bits are whole numbers only
DecimalByteSize.FromBytes(1.5); // Same as constructor
DecimalByteSize.FromKiloBytes(1.5);
DecimalByteSize.FromMegaBytes(1.5);
DecimalByteSize.FromGigaBytes(1.5);
DecimalByteSize.FromTeraBytes(1.5);
```

### Properties

A `ByteSize` object contains representations in `bits`, `bytes`, `kilobytes`, `megabytes`, `gigabytes`, and `terabytes`.

```c#
var maxFileSize = ByteSize.FromKiloBytes(10);
var maxFileSize = DecimalByteSize.FromKiloBytes(10);

maxFileSize.Bits; // 81920
maxFileSize.Bytes; // 10240
maxFileSize.Bits; // 80000
maxFileSize.Bytes; // 10000
maxFileSize.KiloBytes; // 10
maxFileSize.MegaBytes; // 0.009765625
maxFileSize.GigaBytes; // 9.53674316e-6
maxFileSize.TeraBytes; // 9.31322575e-9
maxFileSize.MegaBytes; // 0.01
maxFileSize.GigaBytes; // 1E-05
maxFileSize.TeraBytes; // 1E-08
```

A `ByteSize` object also contains two properties that represent the largest metric prefix symbol and value.

```c#
var maxFileSize = ByteSize.FromKiloBytes(10);
var maxFileSize = DecimalByteSize.FromKiloBytes(10);

maxFileSize.LargestWholeNumberSymbol; // "KB"
maxFileSize.LargestWholeNumberValue; // 10
maxFileSize.LargestWholeNumberSymbol; // "KB"
maxFileSize.LargestWholeNumberValue; // 10
```

### String Representation
Expand All @@ -106,25 +111,32 @@ All string operations are localized to use the number decimal separator of the c
`ByteSize` comes with a handy `ToString` method that uses the largest metric prefix whose value is greater than or equal to 1.

```c#
ByteSize.FromBits(7).ToString(); // 7 b
ByteSize.FromBits(8).ToString(); // 1 B
ByteSize.FromKiloBytes(.5).ToString(); // 512 B
ByteSize.FromKiloBytes(1000).ToString(); // 1000 KB
ByteSize.FromKiloBytes(1024).ToString(); // 1 MB
ByteSize.FromGigabytes(.5).ToString(); // 512 MB
ByteSize.FromGigabytes(1024).ToString(); // 1 TB
DecimalByteSize.FromBits(7).ToString(); // 7 b
DecimalByteSize.FromBits(8).ToString(); // 1 B
DecimalByteSize.FromKiloBytes(.5).ToString(); // 500 B
DecimalByteSize.FromKiloBytes(999).ToString(); // 999 KB
DecimalByteSize.FromKiloBytes(1000).ToString(); // 1 MB
DecimalByteSize.FromGigabytes(.5).ToString(); // 500 MB
DecimalByteSize.FromGigabytes(1000).ToString(); // 1 TB
```

#### Formatting

The `ToString` method accepts a single `string` parameter to format the output. The formatter can contain the symbol of the value to display: `b`, `B`, `KB`, `MB`, `GB`, `TB`. The formatter uses the built in [`double.ToString` method](http://msdn.microsoft.com/en-us/library/kfsatb94\(v=vs.110\).aspx).
The `ToString` method accepts a single `string` parameter to format the output.
The formatter can contain the symbol of the value to display depending on the object:

- `NonStandardByteSize` and `DecimalByteSize`: `b`, `B`, `KB`, `MB`, `GB`, `TB`.
- `BinaryByteSize`: `b`, `B`,`KiB`, `MiB`, `GiB`, `TiB`

The formatter uses the built in [`double.ToString` method](http://msdn.microsoft.com/en-us/library/kfsatb94\(v=vs.110\).aspx).

The default number format is `0.##` which rounds the number to two decimal places and outputs only `0` if the value is `0`.
The default number format is `0.##` which rounds the number to two decimal
places and outputs only `0` if the value is `0`.

You can include symbol and number formats.

```c#
var b = ByteSize.FromKiloBytes(10.505);
var b = DecimalByteSize.FromKiloBytes(10.505);

// Default number format is 0.##
b.ToString("KB"); // 10.52 KB
Expand All @@ -144,7 +156,7 @@ b.ToString("0.00 GB"); // 0 GB
b.ToString("#.## B"); // 10757.12 B

// ByteSize object of value 0
var zeroBytes = ByteSize.FromKiloBytes(0);
var zeroBytes = DecimalByteSize.FromKiloBytes(0);
zeroBytes.ToString(); // 0 b
zeroBytes.ToString("0 kb"); // 0 kb
zeroBytes.ToString("0.## mb"); // 0 mb
Expand All @@ -154,38 +166,40 @@ zeroBytes.ToString("0.## mb"); // 0 mb

`ByteSize` has a `Parse` and `TryParse` method similar to other base classes.

Like other `TryParse` methods, `ByteSize.TryParse` returns `boolean` value indicating whether or not the parsing was successful. If the value is parsed it is output to the `out` parameter supplied.
Like other `TryParse` methods, `DecimalByteSize.TryParse` returns `boolean`
value indicating whether or not the parsing was successful. If the value is
parsed it is output to the `out` parameter supplied.

```c#
ByteSize output;
ByteSize.TryParse("1.5mb", out output);
DecimalByteSize.TryParse("1.5mb", out output);

// Invalid
ByteSize.Parse("1.5 b"); // Can't have partial bits
DecimalByteSize.Parse("1.5 b"); // Can't have partial bits

// Valid
ByteSize.Parse("5b");
ByteSize.Parse("1.55B");
ByteSize.Parse("1.55KB");
ByteSize.Parse("1.55 kB "); // Spaces are trimmed
ByteSize.Parse("1.55 kb");
ByteSize.Parse("1.55 MB");
ByteSize.Parse("1.55 mB");
ByteSize.Parse("1.55 mb");
ByteSize.Parse("1.55 GB");
ByteSize.Parse("1.55 gB");
ByteSize.Parse("1.55 gb");
ByteSize.Parse("1.55 TB");
ByteSize.Parse("1.55 tB");
ByteSize.Parse("1.55 tb");
ByteSize.Parse("1,55 kb"); // de-DE culture
DecimalByteSize.Parse("5b");
DecimalByteSize.Parse("1.55B");
DecimalByteSize.Parse("1.55KB");
DecimalByteSize.Parse("1.55 kB "); // Spaces are trimmed
DecimalByteSize.Parse("1.55 kb");
DecimalByteSize.Parse("1.55 MB");
DecimalByteSize.Parse("1.55 mB");
DecimalByteSize.Parse("1.55 mb");
DecimalByteSize.Parse("1.55 GB");
DecimalByteSize.Parse("1.55 gB");
DecimalByteSize.Parse("1.55 gb");
DecimalByteSize.Parse("1.55 TB");
DecimalByteSize.Parse("1.55 tB");
DecimalByteSize.Parse("1.55 tb");
DecimalByteSize.Parse("1,55 kb"); // de-DE culture
```

#### Author and License

Omar Khudeira ([http://omar.io](http://omar.io))

Copyright (c) 2013-2016 Omar Khudeira. All rights reserved.
Copyright (c) 2013-2018 Omar Khudeira. All rights reserved.

Released under MIT License (see LICENSE file).

68 changes: 68 additions & 0 deletions docs/binary-byte.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Binary Byte

ByteSize started off with:

- 1 kilobyte = 1024 bytes
- 2 letter abbreviation (KB, GB etc.)

See why [here](https://omar.io/2017/01/16/when-technically-right-is-wrong-kilobytes.html).

Since then, there have been a few requests to support the [IEC binary prefix](https://en.wikipedia.org/wiki/Binary_prefix#kibi),
see [issue#1](https://github.com/omar/ByteSize/issues/1) and [Humanizr/Humanizer/issues/592](https://github.com/Humanizr/Humanizer/issues/592).
This document will include the rational for the design of implementing the IEC standard.

## Research

| Vendor | Abbreviation | Ratio | [IEC Standard] | Source
| --- | --- | --- | --- | ---
| ByteSize | KB, GB, etc. | 1 = 1024 | No |
| GCP | KB, GB, etc. | 1 = 1024 | No | https://cloud.google.com/storage/pricing
| AWS | KB, GB, etc. | 1 = 1024 | No | https://aws.amazon.com/ebs/features/
| AWS Glossary | KB, GB, etc. | 1 = 1000 | Yes | https://docs.aws.amazon.com/general/latest/gr/glos-chap.html
| AWS Glossary | KiB, GiB, etc. | 1 = 1024 | Yes | https://docs.aws.amazon.com/general/latest/gr/glos-chap.html
| Azure (RAM) | KiB, GiB, etc. | 1 = 1024 | Yes | https://azure.microsoft.com/en-gb/blog/largest-vm-in-the-cloud/
| Azure (Disk) | KB, GB, etc. | 1 = 1000 | Yes | https://azure.microsoft.com/en-gb/blog/largest-vm-in-the-cloud/
| Mac OS X Leopard + | KB, GB, etc. | 1 = 1000 | Yes | https://support.apple.com/en-us/HT201402
| iOS 11 + | KB, GB, etc. | 1 = 1000 | Yes | https://support.apple.com/en-us/HT201402

In summary, developers are _attempting_ to adhere to the IEC standard by either:

- Changing the abbreviation and ratio to match the IEC binary standard, or;
- Changing the ratio they've used in the past to match the decimal values

[IEC Standard]: https://en.wikipedia.org/wiki/Binary_prefix#kibi

## Implementation Considerations

1. Two structs that represents the decimal (`DecimalByteSize`) and binary
values (`BinaryByteSize`).
- Pros
- Clear separation of what's being represented by the object.
- Solve the namespace collision that forced the `ByteSize` namespace.
- Cons
- Swapping between binary and decimal representation requires two
objects. Maybe can provide a simple way to go from one to the other:
`BinaryByteSize.FromDecimalByteSize` and
`DecimalByteSize.FromBinaryByteSize`. No idea how valuable or needed
this would be though.
- Breaking change since `BinaryByteSize` has the same ratio as the
original `ByteSize` class, but not the text representation.
(See "Backwards Compatibility Note" below).
- Backwards Compatibility Note
- v1.x of ByteSize could ship with two additional structs
`DecimalByteSize` and `BinaryByteSize` but keep `ByteSize` with it's
current implementation.
- v2.0 of ByteSize could break compatiblity and remove the `ByteSize`
struct in favor of `DecimalByteSize` and `BinaryByteSize`.

2. Single struct
- Flat properties (e.g. `value.KiloByte` and `value.KibiByte`).
- Pros
- Single object that allows use of both standards.
- Cons
- Autocomplete lists will have twice as many methods and properties
- Nested properties (e.g. `value.Decimal.KiloByte` and `value.Binary.KibiByte`).
- Pros
- Single object that allows use of both standards.
- Cons
- Autocomplete lists will have twice as many methods and properties
Loading