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 all 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -39,4 +39,4 @@ src/packages/**
*.ncrunch*
*.ndproj
*.nupkg
**/project.lock.json
src/ByteSize/pack
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -4,4 +4,4 @@ services:
- docker

script:
- make build-in-docker
- make test-in-docker
27 changes: 27 additions & 0 deletions Dockerfile
@@ -0,0 +1,27 @@
# Adapted from https://github.com/andrewlock/docker-dotnet-mono/blob/master/Dockerfile
FROM mcr.microsoft.com/dotnet/core/sdk:2.1.700 AS builder

# Install mono
ENV MONO_VERSION 5.18.0.225

RUN apt-get update \
&& apt-get install -y --no-install-recommends gnupg dirmngr \
&& rm -rf /var/lib/apt/lists/* \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --batch --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF \
&& gpg --batch --export --armor 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF > /etc/apt/trusted.gpg.d/mono.gpg.asc \
&& gpgconf --kill all \
&& rm -rf "$GNUPGHOME" \
&& apt-key list | grep Xamarin \
&& apt-get purge -y --auto-remove gnupg dirmngr

RUN echo "deb http://download.mono-project.com/repo/debian stable-stretch/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list \
&& apt-get update \
&& apt-get install -y mono-runtime \
&& rm -rf /var/lib/apt/lists/* /tmp/*

RUN apt-get update \
&& apt-get install -y binutils curl mono-devel ca-certificates-mono fsharp mono-vbnc nuget referenceassemblies-pcl \
&& rm -rf /var/lib/apt/lists/* /tmp/*

WORKDIR /sln
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2013-2017 Omar Khudeira (http://omar.io)
Copyright (c) 2013-2019 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
20 changes: 17 additions & 3 deletions Makefile
@@ -1,3 +1,13 @@
.PHONY: build test pack

# Check to see if Mono exists. If it does, use that path to build against .NET 4.5
ifneq ("$(wildcard /usr/local/lib/mono/)","")
# MONO_REFERENCE_ASSEMBLIES is automatically pulled in ByteSizeLib.csproj
# to allow building against .NET Framework on a Mac where I do most of my
# development.
export MONO_REFERENCE_ASSEMBLIES=/usr/local/lib/mono
endif

build:
dotnet build src

Expand All @@ -7,8 +17,12 @@ test:
pack:
dotnet pack src/ByteSizeLib -c Release -o pack

build-in-docker:
# Use an image with both Mono and .NET Core SDK installed
docker run -td --name bytesize -v $(CURDIR):/bytesize andrewlock/dotnet-mono
test-in-docker:
# Stop and delete conatiner if already exists
docker stop bytesize || true && docker rm bytesize || true
# Use an image with both Mono and .NET Core SDK installed so we can build
# against .NET Framework 4.5
docker build -t bytesize-build .
docker run -td --name bytesize -v $(CURDIR):/bytesize bytesize-build
docker exec bytesize bash -c "cd /bytesize/src && msbuild -t:restore ByteSizeLib.sln"
docker exec bytesize bash -c "cd /bytesize/src && dotnet test ByteSizeLib.Tests"
161 changes: 116 additions & 45 deletions README.md
@@ -1,46 +1,78 @@
# 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/)

#### 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
## v2 Breaking Changes

`ByteSize` assumes `1 kilobyte` = `1024 bytes`. See [why here](http://omar.io/2017/01/16/when-technically-right-is-wrong-kilobytes.html).
### Ratio Changes (HUGE BREAKING CHANGE)

By default `ByteSize` now assumes `1 KB == 1000 B` and `1 KiB == 1024 B` to
adhere to the IEC and NIST standards (https://en.wikipedia.org/wiki/Binary_prefix).
In version 1 `ByteSize` assumed `1 KB == 1024 B`, that means if you're upgrading
from v1, you'll see differences in values.

When you upgrade an existing application to v2 your existing code will be using
the decimal representation of bytes (i.e. `1 KB == 1000 B`). If the difference
in calculation is not material to your application, you don't need to change anything.

However, if you want to use `1 KiB == 1024 B`, then you'll need to change all
`ByteSize` calls to the respective method. For example, calls to
`ByteSize.FromKiloByte` need to be changed to `ByteSize.FromKibiByte`.

Lastly, `ByteSize` no longer supports the ratio of `1 KB == 1024 B`. Note this
is ***kilo***_bytes_ to _bytes_. The only ratio of `1 == 1024` is ***kibi***_bytes_
to _bytes_.

### Other Breaking Changes

- Renamed property `LargestWholeNumberSymbol` and `LargestWholeNumberValue` to `LargestWholeNumberDecimalSymbol` and `LargestWholeNumberDecimalValue` respectively.
- Drop support for all platforms _except_ `netstandard1.0` and `net45`.

## Usage

`ByteSize` adheres to the IEC standard, see this [Wikipedia article](https://en.wikipedia.org/wiki/Kilobyte#Definitions_and_usage).
That means `ByteSize` assumes:

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

`ByteSize` manages conversion of the values internally and provides methods to create and retrieve the values as needed. See the examples below.

### Example

Without `ByteSize`:

```c#
static double MaxFileSizeMBs = 1.5;
double maxFileSizeMBs = 1.5;

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

With `ByteSize`:

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

// I have it in KBs!
MaxFileSize.KiloBytes; // 1536
// I have it in KBs and KiBs!!
maxFileSize.KiloBytes; // 1500
maxFileSize.KibiBytes; // 1464.84376
```

`ByteSize` behaves like any other struct backed by a numerical value.
`ByteSize` behaves like any other struct backed by a numerical value allowing arithmetic operations between two objects.

```c#
// Add
Expand All @@ -62,64 +94,101 @@ 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 ByteSize(15); // Constructor takes in bits (long)
new ByteSize(1.5); // ... or bytes (double)

// Static Constructors
ByteSize.FromBits(10); // Bits are whole numbers only
ByteSize.FromBits(10); // Same as constructor
ByteSize.FromBytes(1.5); // Same as constructor

// Decimal: 1 KB = 1000 B
ByteSize.FromKiloBytes(1.5);
ByteSize.FromMegaBytes(1.5);
ByteSize.FromGigaBytes(1.5);
ByteSize.FromTeraBytes(1.5);

// Binary: 1 KiB = 1024 B
ByteSize.FromKibiBytes(1.5);
ByteSize.FromMebiBytes(1.5);
ByteSize.FromGibiBytes(1.5);
ByteSize.FromTebiBytes(1.5);
```

### Properties

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

- `bits`, `bytes`
- `kilobytes`, `megabytes`, `gigabytes`, and `terabytes`
- `kibibytes`, `mebibytes`, `gibibytes`, and `tebibytes`

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

maxFileSize.Bits; // 81920
maxFileSize.Bytes; // 10240
maxFileSize.Bits; // 80000
maxFileSize.Bytes; // 10000

// Decimal
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

// Binary
maxFileSize.KibiBytes; // 9.765625
maxFileSize.MebiBytes; // 0.0095367431640625
maxFileSize.GibiBytes; // 9.31322574615479E-06
maxFileSize.TebiBytes; // 9.09494701772928E-09
```

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

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

maxFileSize.LargestWholeNumberSymbol; // "KB"
maxFileSize.LargestWholeNumberValue; // 10
maxFileSize.LargestWholeNumberDecimalSymbol; // "KB"
maxFileSize.LargestWholeNumberDecimalValue; // 10
maxFileSize.LargestWholeNumberBinarySymbol; // "KiB"
maxFileSize.LargestWholeNumberBinaryValue; // 9.765625
```

### String Representation

By default a `ByteSize` object uses the decimal value for string representation.

All string operations are localized to use the number decimal separator of the culture set in `Thread.CurrentThread.CurrentCulture`.

#### ToString

`ByteSize` comes with a handy `ToString` method that uses the largest metric prefix whose value is greater than or equal to 1.

```c#
// By default the decimal values are used
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
ByteSize.FromKiloBytes(.5).ToString(); // 500 B
ByteSize.FromKiloBytes(999).ToString(); // 999 KB
ByteSize.FromKiloBytes(1000).ToString(); // 1 MB
ByteSize.FromGigabytes(.5).ToString(); // 500 MB
ByteSize.FromGigabytes(1000).ToString(); // 1 TB

// Binary
ByteSize.Parse("1.55 kb").ToString("kib"); // 1.51 kib
```

#### 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.

- Base: `b`, `B`
- Decimal: `KB`, `MB`, `GB`, `TB`
- Binary: `KiB`, `MiB`, `GiB`, `TiB`

The default number format is `0.##` which rounds the number to two decimal places and outputs only `0` if the value is `0`.
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`.

You can include symbol and number formats.

Expand Down Expand Up @@ -154,11 +223,14 @@ 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, `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.

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

// Invalid
ByteSize.Parse("1.5 b"); // Can't have partial bits
Expand All @@ -174,18 +246,17 @@ 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
ByteSize.Parse("1.55 gib");
ByteSize.Parse("1.55 TiB");
ByteSize.Parse("1.55 tiB");
ByteSize.Parse("1.55 tib");
ByteSize.Parse("1,55 kib"); // 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-2019 Omar Khudeira. All rights reserved.

Released under MIT License (see LICENSE file).