diff --git a/.github/workflows/datasize.yml b/.github/workflows/datasize.yml
index 1f46ec3..620e96a 100644
--- a/.github/workflows/datasize.yml
+++ b/.github/workflows/datasize.yml
@@ -2,9 +2,9 @@ name: datasize
on:
push:
- branches: ["main"]
+ branches: ["main", "release/**"]
pull_request:
- branches: ["main", "feat/**"]
+ branches: ["main", "feat/**", "release/**"]
jobs:
build:
diff --git a/README.md b/README.md
index 6b1a223..862d586 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,6 @@ Use extension function to create the `DataSize` representation. It's important t
`bytes`.
To explore all available features and implementation details, check out the full class documentation.
-All calculations are based
-on [IBM Storage Insights](https://www.ibm.com/docs/en/storage-insights?topic=overview-units-measurement-storage-data).
-
## Table of Contents
* [Setup](#setup)
@@ -24,6 +21,8 @@ on [IBM Storage Insights](https://www.ibm.com/docs/en/storage-insights?topic=ove
* [More to Explore](#more-to-explore)
## Setup
+The latest version of the library
+[](https://mvnrepository.com/artifact/io.github.ardiien.datasize/datasize)
Kotlin DSL:
@@ -51,18 +50,18 @@ dependencies {
## Basics
-The `DataSize` has extensions available on numeric types like `Int`, `Long`, and `Double`.
+The `DataSize` has extensions available on numeric types like `Int`, `Long`, and `Double`. Also, supports `ByteArray`.
Here is a small example.
```kotlin
import io.github.ardiien.datasize.*
fun main() {
- // Supported units: bytes, kilobytes, megabytes, gigabytes, and terabytes.
+ // Supported units: bytes, kilobytes, kibibytes, megabytes, mebibytes, etc.
- val kilobyteFromInt = 1.kilobytes // 1024
- val kilobyteFromDouble = 1.0.kilobytes // 1024
- val kilobyteFromLong = 1L.kilobytes // 1024
+ val kilobyteFromInt = 1.binary.kibibytes // 1024
+ val kilobyteFromDouble = 1.0.decimal.kilobytes // 1024
+ val kilobyteFromLong = 1L.binary.kibibytes // 1024
}
```
@@ -78,12 +77,12 @@ import io.github.ardiien.datasize.*
fun main() {
// Explore all overrides of the "operator fun" in DataSize class.
- val addition = 5.megabytes + 15.megabytes // 20 MB
- val substraction = 105.megabytes - 5.megabytes // 100 MB
+ val addition = 5.binary.mebibytes + 15.binary.mebibytes // 20 MB
+ val substraction = 105.decimal.megabytes - 5.decimal.megabytes // 100 MB
- val multiplication = 5.megabytes * 2 // 10 MB
- val division = 15.megabytes / 2 // 7,5 MB
- val remainder = 11.megabytes % 2.megabytes // 1 MB
+ val multiplication = 5.decimal.megabytes * 2 // 10 MB
+ val division = 15.binary.mebibytes / 2 // 7,5 MB
+ val remainder = 11.decimal.megabytes % 2.decimal.megabytes // 1 MB
}
```
@@ -107,16 +106,16 @@ import io.github.ardiien.datasize.*
fun main() {
val sortedList = listOf(
- 1.kilobytes, 1.megabytes, 20.kilobytes // [1024, 20480, 1048576]
+ 1.binary.kibibytes, 1.binary.mebibytes, 20.binary.kibibytes // [1024, 20480, 1048576]
).sorted()
- val gt = 15.kilobytes > 1.kilobytes // true
- val lte = 15.kilobytes <= 14.kilobytes // false
- val eq = 15.kilobytes == 15.kilobytes // true
- val neq = 15.kilobytes != 5.kilobytes // true
+ val gt = 15.binary.kibibytes > 1.binary.kibibytes // true
+ val lte = 15.binary.kibibytes <= 14.binary.kibibytes // false
+ val eq = 15.binary.kibibytes == 15.binary.kibibytes // true
+ val neq = 15.binary.kibibytes != 5.binary.kibibytes // true
- val min = min(2.megabytes, 2.kilobytes) // 2048
- val max = max(2.megabytes, 2.kilobytes) // 2097152
+ val min = min(2.binary.mebibytes, 2.binary.kibibytes) // 2048
+ val max = max(2.binary.mebibytes, 2.binary.kibibytes) // 2097152
}
```
@@ -129,12 +128,8 @@ bits (0s and 1s). You can have a certain number of bits, but you can't have a ne
## Formatting
-The `DataSize` class supports formatting in both Decimal (`1000`) as _experimental_ and Binary (`1024`) base
-representations. By default, `DataSizeUnit` uses the **Binary** base.
-Currently, there is no way to select the base for the `DataSize`. To convert `DataSize` Binary to Decimal, use
-`DataSize.toDecimalString` helper function exclusively for display purposes.
-
-To remove boilerplate and repeated code, you can use the utility class `DataSizeFormatter`. There are two types of
+The `DataSize` class supports formatting in both Decimal (`1000`) and Binary (`1024`) base
+representations. To remove boilerplate and repeated code, you can use the utility class `DataSizeFormatter`. There are two types of
operations:
1. `format` – transforms a `DataSize` instance or raw bytes into a formatted `String`
@@ -149,11 +144,12 @@ clamped between 0 and 2. Any value greater than 2 will be coerced to 2 during ev
import io.github.ardiien.datasize.*
fun main() {
- val value = 55.563.kilobytes
+ val formatter = DefaultDataSizeFormatter(DefaultDataSizeFormatter.createFormat())
+ val value = 55.563.binary.kibibytes
- val defaultPrecision = DataSizeFormatter.format(value, decimals = 0) // 56 KB, default
- val betterPrecision = DataSizeFormatter.format(value, decimals = 1) // 55,6 KB
- val maxAvailablePrecision = DataSizeFormatter.format(value, decimals = 2) // 55,56 KB, max allowed
+ val defaultPrecision = formatter.format(value, fractionDigits = 0) // 56 KB, default
+ val betterPrecision = formatter.format(value, fractionDigits = 1) // 55,6 KB
+ val maxAvailablePrecision = formatter.format(value, fractionDigits = 2) // 55,56 KB, max allowed
}
```
diff --git a/datasize-core/api/datasize-core.api b/datasize-core/api/datasize-core.api
index bf900b2..b0ca492 100644
--- a/datasize-core/api/datasize-core.api
+++ b/datasize-core/api/datasize-core.api
@@ -1,100 +1,233 @@
public final class io/github/ardiien/datasize/DataSize : java/lang/Comparable {
public static final field Companion Lio/github/ardiien/datasize/DataSize$Companion;
- public static final synthetic fun box-impl (J)Lio/github/ardiien/datasize/DataSize;
+ public fun compareTo (Lio/github/ardiien/datasize/DataSize;)I
public synthetic fun compareTo (Ljava/lang/Object;)I
- public fun compareTo-BuKYR8w (J)I
- public static fun compareTo-BuKYR8w (JJ)I
- public static final fun div-BuKYR8w (JJ)D
- public static final fun div-_mO8CwU (JD)J
- public static final fun div-_mO8CwU (JI)J
+ public final fun div (D)Lio/github/ardiien/datasize/DataSize;
+ public final fun div (I)Lio/github/ardiien/datasize/DataSize;
public fun equals (Ljava/lang/Object;)Z
- public static fun equals-impl (JLjava/lang/Object;)Z
- public static final fun equals-impl0 (JJ)Z
- public static final fun getInBytes-impl (J)J
- public static final fun getInGigabytes-impl (J)D
- public static final fun getInKilobytes-impl (J)D
- public static final fun getInMegabytes-impl (J)D
- public static final fun getInTerabytes-impl (J)D
+ public final fun getInBytes ()J
+ public final fun getInGibibytes ()D
+ public final fun getInGigabytes ()D
+ public final fun getInKibibytes ()D
+ public final fun getInKilobytes ()D
+ public final fun getInMebibytes ()D
+ public final fun getInMegabytes ()D
+ public final fun getInPebibytes ()D
+ public final fun getInPetabytes ()D
+ public final fun getInTebibytes ()D
+ public final fun getInTerabytes ()D
public fun hashCode ()I
- public static fun hashCode-impl (J)I
- public static final fun isInfinite-impl (J)Z
- public static final fun isZero-impl (J)Z
- public static final fun minus-nBzHbaQ (JJ)J
- public static final fun plus-nBzHbaQ (JJ)J
- public static final fun rem-nBzHbaQ (JJ)J
- public static final fun times-_mO8CwU (JD)J
- public static final fun times-_mO8CwU (JI)J
- public static final fun toDecimalString-impl (JLio/github/ardiien/datasize/DataSizeUnit;I)Ljava/lang/String;
- public static synthetic fun toDecimalString-impl$default (JLio/github/ardiien/datasize/DataSizeUnit;IILjava/lang/Object;)Ljava/lang/String;
- public static final fun toDouble-impl (JLio/github/ardiien/datasize/DataSizeUnit;)D
- public static final fun toInt-impl (JLio/github/ardiien/datasize/DataSizeUnit;)I
- public static final fun toLong-impl (JLio/github/ardiien/datasize/DataSizeUnit;)J
+ public final fun isBinary ()Z
+ public final fun isDecimal ()Z
+ public final fun isInfinite ()Z
+ public final fun isZero ()Z
+ public final fun minus (Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/DataSize;
+ public final fun plus (Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/DataSize;
+ public final fun rem (Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/DataSize;
+ public final fun times (D)Lio/github/ardiien/datasize/DataSize;
+ public final fun times (I)Lio/github/ardiien/datasize/DataSize;
+ public final fun toDouble (Lio/github/ardiien/datasize/unit/DataSizeUnit;)D
+ public final fun toInt (Lio/github/ardiien/datasize/unit/DataSizeUnit;)I
+ public final fun toLong (Lio/github/ardiien/datasize/unit/DataSizeUnit;)J
public fun toString ()Ljava/lang/String;
- public static fun toString-impl (J)Ljava/lang/String;
- public static final fun toString-impl (JLio/github/ardiien/datasize/DataSizeUnit;I)Ljava/lang/String;
- public static synthetic fun toString-impl$default (JLio/github/ardiien/datasize/DataSizeUnit;IILjava/lang/Object;)Ljava/lang/String;
- public final synthetic fun unbox-impl ()J
+ public final fun toString (Lio/github/ardiien/datasize/unit/DataSizeUnit;ILio/github/ardiien/datasize/DataSizeFormatter;)Ljava/lang/String;
+ public static synthetic fun toString$default (Lio/github/ardiien/datasize/DataSize;Lio/github/ardiien/datasize/unit/DataSizeUnit;ILio/github/ardiien/datasize/DataSizeFormatter;ILjava/lang/Object;)Ljava/lang/String;
}
public final class io/github/ardiien/datasize/DataSize$Companion {
- public final fun convert (DLio/github/ardiien/datasize/DataSizeUnit;Lio/github/ardiien/datasize/DataSizeUnit;I)D
- public static synthetic fun convert$default (Lio/github/ardiien/datasize/DataSize$Companion;DLio/github/ardiien/datasize/DataSizeUnit;Lio/github/ardiien/datasize/DataSizeUnit;IILjava/lang/Object;)D
- public final fun getBytes-_mO8CwU (D)J
- public final fun getBytes-_mO8CwU (I)J
- public final fun getBytes-_mO8CwU (J)J
- public final fun getGigabytes-_mO8CwU (D)J
- public final fun getGigabytes-_mO8CwU (I)J
- public final fun getGigabytes-_mO8CwU (J)J
- public final fun getInfinite-3VZmxlE ()J
- public final fun getKilobytes-_mO8CwU (D)J
- public final fun getKilobytes-_mO8CwU (I)J
- public final fun getKilobytes-_mO8CwU (J)J
- public final fun getMegabytes-_mO8CwU (D)J
- public final fun getMegabytes-_mO8CwU (I)J
- public final fun getMegabytes-_mO8CwU (J)J
- public final fun getTerabytes-_mO8CwU (D)J
- public final fun getTerabytes-_mO8CwU (I)J
- public final fun getTerabytes-_mO8CwU (J)J
- public final fun getZero-3VZmxlE ()J
-}
-
-public final class io/github/ardiien/datasize/DataSizeFormatter {
- public static final field INSTANCE Lio/github/ardiien/datasize/DataSizeFormatter;
- public static final fun format (JLio/github/ardiien/datasize/DataSizeUnit;I)Ljava/lang/String;
- public static synthetic fun format$default (JLio/github/ardiien/datasize/DataSizeUnit;IILjava/lang/Object;)Ljava/lang/String;
- public final synthetic fun format-wBTIb-E (JLio/github/ardiien/datasize/DataSizeUnit;I)Ljava/lang/String;
- public static synthetic fun format-wBTIb-E$default (Lio/github/ardiien/datasize/DataSizeFormatter;JLio/github/ardiien/datasize/DataSizeUnit;IILjava/lang/Object;)Ljava/lang/String;
- public static final fun unitFrom (J)Lio/github/ardiien/datasize/DataSizeUnit;
- public final synthetic fun unitFrom-BuKYR8w (J)Lio/github/ardiien/datasize/DataSizeUnit;
+ public final fun getBinary (D)Lio/github/ardiien/datasize/builder/BinaryNumberDataSizeBuilder;
+ public final fun getBinary (I)Lio/github/ardiien/datasize/builder/BinaryNumberDataSizeBuilder;
+ public final fun getBinary (J)Lio/github/ardiien/datasize/builder/BinaryNumberDataSizeBuilder;
+ public final fun getBinary ([B)Lio/github/ardiien/datasize/builder/BinaryArrayDataSizeBuilder;
+ public final fun getDecimal (D)Lio/github/ardiien/datasize/builder/DecimalNumberDataSizeBuilder;
+ public final fun getDecimal (I)Lio/github/ardiien/datasize/builder/DecimalNumberDataSizeBuilder;
+ public final fun getDecimal (J)Lio/github/ardiien/datasize/builder/DecimalNumberDataSizeBuilder;
+ public final fun getDecimal ([B)Lio/github/ardiien/datasize/builder/DecimalArrayDataSizeBuilder;
}
-public final class io/github/ardiien/datasize/DataSizeKt {
- public static final fun max-yHBNY8A (JJ)J
- public static final fun min-yHBNY8A (JJ)J
- public static final fun orZero-SwkpY8w (Lio/github/ardiien/datasize/DataSize;)J
- public static final fun toDataSize (DLio/github/ardiien/datasize/DataSizeUnit;)J
- public static final fun toDataSize (ILio/github/ardiien/datasize/DataSizeUnit;)J
- public static final fun toDataSize (JLio/github/ardiien/datasize/DataSizeUnit;)J
+public final class io/github/ardiien/datasize/DataSize$binary {
+ public static final field INSTANCE Lio/github/ardiien/datasize/DataSize$binary;
+ public final fun getInfinite ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getZero ()Lio/github/ardiien/datasize/DataSize;
+}
+
+public final class io/github/ardiien/datasize/DataSize$decimal {
+ public static final field INSTANCE Lio/github/ardiien/datasize/DataSize$decimal;
+ public final fun getInfinite ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getZero ()Lio/github/ardiien/datasize/DataSize;
}
-public final class io/github/ardiien/datasize/DataSizeUnit : java/lang/Enum {
- public static final field Bytes Lio/github/ardiien/datasize/DataSizeUnit;
- public static final field Gigabytes Lio/github/ardiien/datasize/DataSizeUnit;
- public static final field Kilobytes Lio/github/ardiien/datasize/DataSizeUnit;
- public static final field Megabytes Lio/github/ardiien/datasize/DataSizeUnit;
- public static final field Terabytes Lio/github/ardiien/datasize/DataSizeUnit;
- public static fun getEntries ()Lkotlin/enums/EnumEntries;
- public static fun valueOf (Ljava/lang/String;)Lio/github/ardiien/datasize/DataSizeUnit;
- public static fun values ()[Lio/github/ardiien/datasize/DataSizeUnit;
+public abstract interface class io/github/ardiien/datasize/DataSizeFormatter {
+ public abstract fun format (Lio/github/ardiien/datasize/DataSize;Lio/github/ardiien/datasize/unit/DataSizeUnit;I)Ljava/lang/String;
+ public static synthetic fun format$default (Lio/github/ardiien/datasize/DataSizeFormatter;Lio/github/ardiien/datasize/DataSize;Lio/github/ardiien/datasize/unit/DataSizeUnit;IILjava/lang/Object;)Ljava/lang/String;
+ public abstract fun unitFrom (Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/unit/DataSizeUnit;
}
-public final class io/github/ardiien/datasize/DataSizeUnitKt {
- public static final fun toBinaryUnit (DLio/github/ardiien/datasize/DataSizeUnit;)D
- public static final fun toBinaryUnit (ILio/github/ardiien/datasize/DataSizeUnit;)D
- public static final fun toDecimalUnit (DLio/github/ardiien/datasize/DataSizeUnit;)D
- public static final fun toDecimalUnit (ILio/github/ardiien/datasize/DataSizeUnit;)D
+public final class io/github/ardiien/datasize/DataSizeFormatter$DefaultImpls {
+ public static synthetic fun format$default (Lio/github/ardiien/datasize/DataSizeFormatter;Lio/github/ardiien/datasize/DataSize;Lio/github/ardiien/datasize/unit/DataSizeUnit;IILjava/lang/Object;)Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/DataSizeKt {
+ public static final fun max (Lio/github/ardiien/datasize/DataSize;Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/DataSize;
+ public static final fun min (Lio/github/ardiien/datasize/DataSize;Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/DataSize;
+ public static final fun orBinaryZero (Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/DataSize;
+ public static final fun orDecimalZero (Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/DataSize;
}
public abstract interface annotation class io/github/ardiien/datasize/ExperimentalDataSizeApi : java/lang/annotation/Annotation {
}
+public final class io/github/ardiien/datasize/builder/BinaryArrayDataSizeBuilder {
+ public fun (Ljava/lang/Number;)V
+ public final fun getBytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun toDataSize (Lio/github/ardiien/datasize/unit/BinaryUnit;)Lio/github/ardiien/datasize/DataSize;
+}
+
+public final class io/github/ardiien/datasize/builder/BinaryNumberDataSizeBuilder {
+ public fun (Ljava/lang/Number;)V
+ public final fun getBytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getGibibytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getKibibytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getMebibytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getPebibytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getTebibytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun toDataSize (Lio/github/ardiien/datasize/unit/BinaryUnit;)Lio/github/ardiien/datasize/DataSize;
+}
+
+public final class io/github/ardiien/datasize/builder/DecimalArrayDataSizeBuilder {
+ public fun (Ljava/lang/Number;)V
+ public final fun getBytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun toDataSize (Lio/github/ardiien/datasize/unit/DecimalUnit;)Lio/github/ardiien/datasize/DataSize;
+}
+
+public final class io/github/ardiien/datasize/builder/DecimalNumberDataSizeBuilder {
+ public fun (Ljava/lang/Number;)V
+ public final fun getBytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getGigabytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getKilobytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getMegabytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getPetabytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun getTerabytes ()Lio/github/ardiien/datasize/DataSize;
+ public final fun toDataSize (Lio/github/ardiien/datasize/unit/DecimalUnit;)Lio/github/ardiien/datasize/DataSize;
+}
+
+public final class io/github/ardiien/datasize/formatter/DefaultDataSizeFormatter : io/github/ardiien/datasize/DataSizeFormatter {
+ public static final field Companion Lio/github/ardiien/datasize/formatter/DefaultDataSizeFormatter$Companion;
+ public fun (Ljava/text/DecimalFormat;)V
+ public fun format (Lio/github/ardiien/datasize/DataSize;Lio/github/ardiien/datasize/unit/DataSizeUnit;I)Ljava/lang/String;
+ public fun unitFrom (Lio/github/ardiien/datasize/DataSize;)Lio/github/ardiien/datasize/unit/DataSizeUnit;
+}
+
+public final class io/github/ardiien/datasize/formatter/DefaultDataSizeFormatter$Companion {
+ public final fun createFormat (Ljava/math/RoundingMode;IZLjava/text/DecimalFormatSymbols;)Ljava/text/DecimalFormat;
+ public static synthetic fun createFormat$default (Lio/github/ardiien/datasize/formatter/DefaultDataSizeFormatter$Companion;Ljava/math/RoundingMode;IZLjava/text/DecimalFormatSymbols;ILjava/lang/Object;)Ljava/text/DecimalFormat;
+ public final fun createFormatSymbols (CC)Ljava/text/DecimalFormatSymbols;
+ public static synthetic fun createFormatSymbols$default (Lio/github/ardiien/datasize/formatter/DefaultDataSizeFormatter$Companion;CCILjava/lang/Object;)Ljava/text/DecimalFormatSymbols;
+}
+
+public abstract class io/github/ardiien/datasize/unit/BinaryUnit : io/github/ardiien/datasize/unit/DataSizeUnit {
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;DILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun entries ()Lkotlinx/collections/immutable/ImmutableList;
+}
+
+public final class io/github/ardiien/datasize/unit/BinaryUnit$Byte : io/github/ardiien/datasize/unit/BinaryUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/BinaryUnit$Byte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/BinaryUnit$Gibibyte : io/github/ardiien/datasize/unit/BinaryUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/BinaryUnit$Gibibyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/BinaryUnit$Kibibyte : io/github/ardiien/datasize/unit/BinaryUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/BinaryUnit$Kibibyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/BinaryUnit$Mebibyte : io/github/ardiien/datasize/unit/BinaryUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/BinaryUnit$Mebibyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/BinaryUnit$Pebibyte : io/github/ardiien/datasize/unit/BinaryUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/BinaryUnit$Pebibyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/BinaryUnit$Tebibyte : io/github/ardiien/datasize/unit/BinaryUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/BinaryUnit$Tebibyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public abstract class io/github/ardiien/datasize/unit/DataSizeUnit {
+ public final fun abbreviation ()Ljava/lang/String;
+ public fun entries ()Lkotlinx/collections/immutable/ImmutableList;
+ public final fun name ()Ljava/lang/String;
+ public final fun value ()Ljava/math/BigDecimal;
+}
+
+public final class io/github/ardiien/datasize/unit/DataSizeUnitKt {
+ public static final fun isBinaryUnit (Lio/github/ardiien/datasize/unit/DataSizeUnit;)Z
+ public static final fun isDecimalUnit (Lio/github/ardiien/datasize/unit/DataSizeUnit;)Z
+}
+
+public abstract class io/github/ardiien/datasize/unit/DecimalUnit : io/github/ardiien/datasize/unit/DataSizeUnit {
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;DILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun entries ()Lkotlinx/collections/immutable/ImmutableList;
+}
+
+public final class io/github/ardiien/datasize/unit/DecimalUnit$Byte : io/github/ardiien/datasize/unit/DecimalUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/DecimalUnit$Byte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/DecimalUnit$Gigabyte : io/github/ardiien/datasize/unit/DecimalUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/DecimalUnit$Gigabyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/DecimalUnit$Kilobyte : io/github/ardiien/datasize/unit/DecimalUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/DecimalUnit$Kilobyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/DecimalUnit$Megabyte : io/github/ardiien/datasize/unit/DecimalUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/DecimalUnit$Megabyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/DecimalUnit$Petabyte : io/github/ardiien/datasize/unit/DecimalUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/DecimalUnit$Petabyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/github/ardiien/datasize/unit/DecimalUnit$Terabyte : io/github/ardiien/datasize/unit/DecimalUnit {
+ public static final field INSTANCE Lio/github/ardiien/datasize/unit/DecimalUnit$Terabyte;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
diff --git a/datasize-core/build.gradle.kts b/datasize-core/build.gradle.kts
index fec62f6..8308f78 100644
--- a/datasize-core/build.gradle.kts
+++ b/datasize-core/build.gradle.kts
@@ -9,7 +9,7 @@ plugins {
}
group = "io.github.ardiien.datasize"
-version = "1.0.0"
+version = "2.0.0-alpha01"
java {
withSourcesJar()
@@ -34,7 +34,7 @@ tasks.test {
}
dependencies {
- implementation(libs.kotlin.stdlib)
+ implementation(libs.kotlinx.collections)
testImplementation(libs.kotlin.test)
}
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSize.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSize.kt
index 2b81119..5f1a9ad 100644
--- a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSize.kt
+++ b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSize.kt
@@ -5,133 +5,129 @@
*/
package io.github.ardiien.datasize
-import java.math.RoundingMode
-import java.text.DecimalFormat
-import java.text.DecimalFormatSymbols
-import kotlin.math.floor
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.math.pow
+import io.github.ardiien.datasize.DataSize.Companion.binary
+import io.github.ardiien.datasize.DataSize.Companion.decimal
+import io.github.ardiien.datasize.builder.BinaryArrayDataSizeBuilder
+import io.github.ardiien.datasize.builder.BinaryNumberDataSizeBuilder
+import io.github.ardiien.datasize.builder.DecimalArrayDataSizeBuilder
+import io.github.ardiien.datasize.builder.DecimalNumberDataSizeBuilder
+import io.github.ardiien.datasize.formatter.DefaultDataSizeFormatter
+import io.github.ardiien.datasize.unit.BinaryUnit
+import io.github.ardiien.datasize.unit.DataSizeUnit
+import io.github.ardiien.datasize.unit.DecimalUnit
+import io.github.ardiien.datasize.unit.DivisionMathContext
+import io.github.ardiien.datasize.unit.UnitMathContext
+import io.github.ardiien.datasize.unit.convertDataSizeUnit
+import io.github.ardiien.datasize.unit.isBinaryUnit
+import io.github.ardiien.datasize.unit.isDecimalUnit
+import java.math.BigDecimal
import kotlin.math.roundToInt
-import kotlin.math.roundToLong
/**
- * Represents a data size in various units (e.g., bytes, kilobytes, megabytes, etc.).
+ * Represents a data size as a numeric value expressed in bytes with an associated [DataSizeUnit].
*
- * This class provides methods for conversion between different data size units.
- * To construct a [DataSize] use either the extension function [toDataSize],
- * or the extension properties bytes, kilobytes, and megabytes, available on
- * Int, Long, and Double numeric types.
+ * A [DataSize] is a value object whose magnitude is defined by its underlying byte value.
+ * All comparisons and arithmetic operations are performed using this canonical representation,
+ * regardless of the unit used to construct the instance.
*
- * To get the value of this [DataSize] expressed in a particular [DataSizeUnit]s use the functions
- * toInt, toLong, and toDouble or the properties inBytes, inKilobytes, and inMegabytes.
+ * Instances are created via builder extensions:
+ * - [Long.binary], [Int.binary], [Double.binary], [ByteArray.binary].
+ * - [Long.decimal], [Int.decimal], [Double.decimal], [ByteArray.decimal].
*
- * For more information about unit calculations, see [Storage Insights](https://www.ibm.com/docs/en/storage-insights?topic=overview-units-measurement-storage-data).
+ * Conversion to other units is available through:
+ * - [toInt], [toLong], [toDouble].
+ * - [inBytes], [inKilobytes], [inMegabytes], and related properties.
*/
-@JvmInline
-public value class DataSize internal constructor(
- private val rawValue: Long
+public class DataSize internal constructor(
+ internal val rawValue: BigDecimal,
+ internal val unit: DataSizeUnit,
) : Comparable {
- private val sizeUnit
- get() = DataSizeUnit.Bytes
init {
- check(rawValue in 0..MAX_SIZE) {
+ check(rawValue in BigDecimal.ZERO..MAX_SIZE) {
"DataSize must be in range 0 <= $rawValue <= $MAX_SIZE."
}
}
- public companion object {
- /** The size equal to exactly 0 bytes. */
- public val Zero: DataSize = DataSize(rawValue = 0)
-
- /** The size whose value is positive infinity. It is useful for representing unlimited size. */
- public val Infinite: DataSize = DataSize(rawValue = MAX_SIZE)
-
-
- /** Returns a [DataSize] equal to this [Long] number of terabytes. */
- public inline val Long.terabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Terabytes)
-
- /** Returns a [DataSize] equal to this [Int] number of terabytes. */
- public inline val Int.terabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Terabytes)
-
- /** Returns a [DataSize] equal to this [Double] number of terabytes. */
- public inline val Double.terabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Terabytes)
-
-
- /** Returns a [DataSize] equal to this [Long] number of gigabytes. */
- public inline val Long.gigabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Gigabytes)
+ /**
+ * Returns `true` if both values represent the same number of bytes.
+ * The unit is not considered for equality.
+ */
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is DataSize) return false
- /** Returns a [DataSize] equal to this [Int] number of gigabytes. */
- public inline val Int.gigabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Gigabytes)
+ return this.rawValue.compareTo(other.rawValue) == 0
+ }
- /** Returns a [DataSize] equal to this [Double] number of gigabytes. */
- public inline val Double.gigabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Gigabytes)
+ /**
+ * Returns a hash code based on the underlying byte value.
+ *
+ * The unit is not included to preserve consistency with [equals].
+ */
+ override fun hashCode(): Int = rawValue.hashCode()
+ /** Predefined values for the binary (IEC) unit system. */
+ @Suppress("ClassName")
+ public object binary {
- /** Returns a [DataSize] equal to this [Long] number of megabytes. */
- public inline val Long.megabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Megabytes)
+ /** The size equal to exactly 0 bytes. */
+ public val Zero: DataSize = DataSize(rawValue = BigDecimal.ZERO, unit = BinaryUnit.Byte)
- /** Returns a [DataSize] equal to this [Int] number of megabytes. */
- public inline val Int.megabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Megabytes)
+ /** The size whose value is positive infinity. It is useful for representing unlimited size. */
+ public val Infinite: DataSize = DataSize(rawValue = MAX_SIZE, unit = BinaryUnit.Byte)
+ }
- /** Returns a [DataSize] equal to this [Double] number of megabytes. */
- public inline val Double.megabytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Megabytes)
+ /** Predefined values for the decimal (SI) unit system. */
+ @Suppress("ClassName")
+ public object decimal {
+ /** The size equal to exactly 0 bytes. */
+ public val Zero: DataSize = DataSize(rawValue = BigDecimal.ZERO, unit = DecimalUnit.Byte)
- /** Returns a [DataSize] equal to this [Long] number of kilobytes. */
- public inline val Long.kilobytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Kilobytes)
+ /** The size whose value is positive infinity. It is useful for representing unlimited size. */
+ public val Infinite: DataSize = DataSize(rawValue = MAX_SIZE, unit = DecimalUnit.Byte)
+ }
- /** Returns a [DataSize] equal to this [Int] number of kilobytes. */
- public inline val Int.kilobytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Kilobytes)
+ public companion object {
- /** Returns a [DataSize] equal to this [Double] number of kilobytes. */
- public inline val Double.kilobytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Kilobytes)
+ /** Returns a [BinaryNumberDataSizeBuilder] initialized with this value. */
+ public inline val Long.binary: BinaryNumberDataSizeBuilder
+ get() = BinaryNumberDataSizeBuilder(this)
+ /** Returns a [BinaryNumberDataSizeBuilder] initialized with this value. */
+ public inline val Int.binary: BinaryNumberDataSizeBuilder
+ get() = BinaryNumberDataSizeBuilder(this)
- /** Returns a [DataSize] equal to this [Long] number of bytes. */
- public inline val Long.bytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Bytes)
+ /** Returns a [BinaryNumberDataSizeBuilder] initialized with this value. */
+ public inline val Double.binary: BinaryNumberDataSizeBuilder
+ get() = BinaryNumberDataSizeBuilder(this)
- /** Returns a [DataSize] equal to this [Int] number of bytes. */
- public inline val Int.bytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Bytes)
+ /** Returns a [BinaryArrayDataSizeBuilder] initialized with this value. */
+ public inline val ByteArray.binary: BinaryArrayDataSizeBuilder
+ get() = BinaryArrayDataSizeBuilder(this.size)
- /** Returns a [DataSize] equal to this [Double] number of bytes. */
- public inline val Double.bytes: DataSize
- get() = toDataSize(unit = DataSizeUnit.Bytes)
+ /** Returns a [DecimalNumberDataSizeBuilder] initialized with this value. */
+ public inline val Long.decimal: DecimalNumberDataSizeBuilder
+ get() = DecimalNumberDataSizeBuilder(this)
+ /** Returns a [DecimalNumberDataSizeBuilder] initialized with this value. */
+ public inline val Int.decimal: DecimalNumberDataSizeBuilder
+ get() = DecimalNumberDataSizeBuilder(this)
- /** Returns a number directly converted from [sourceUnit] to [targetUnit] type. */
- public fun convert(
- value: Double,
- sourceUnit: DataSizeUnit,
- targetUnit: DataSizeUnit,
- precision: Int = 2,
- ): Double = convertDataSizeUnit(value, sourceUnit, targetUnit)
- .roundTo(precision)
+ /** Returns a [DecimalNumberDataSizeBuilder] initialized with this value. */
+ public inline val Double.decimal: DecimalNumberDataSizeBuilder
+ get() = DecimalNumberDataSizeBuilder(this)
- private fun Double.roundTo(precision: Int): Double {
- val factor = 10.0.pow(precision.toDouble())
- return floor(this * factor) / factor
- }
+ /** Returns a [DecimalArrayDataSizeBuilder] initialized with this value. */
+ public inline val ByteArray.decimal: DecimalArrayDataSizeBuilder
+ get() = DecimalArrayDataSizeBuilder(this.size)
}
+
public operator fun rem(other: DataSize): DataSize =
- dataSizeOf(this.rawValue.rem(other.rawValue))
+ normalizedDataSizeOf(rawValue.remainder(other.rawValue, DivisionMathContext), unit)
/**
* Divides the [rawValue] by the given [scale] and returns the result as [DataSize].
@@ -142,8 +138,8 @@ public value class DataSize internal constructor(
public operator fun div(scale: Int): DataSize {
require(scale > 0) { "scale $scale must be a positive value to perform division" }
- val result = rawValue / scale
- return dataSizeOf(value = result)
+ val result = rawValue.divide(BigDecimal(scale, UnitMathContext), DivisionMathContext)
+ return normalizedDataSizeOf(result, unit)
}
/**
@@ -161,32 +157,15 @@ public value class DataSize internal constructor(
require(scale > 0) { "scale $scale must be a positive value to perform division" }
- val unit = sizeUnit
- val result = toDouble(unit) / scale
- return result.toDataSize(unit = unit)
- }
-
- /**
- * Divides the [rawValue], converted to a [Double], by the given [DataSize],
- * and returns the result as a [Double] in the larger [sizeUnit] of the two.
- *
- * @param other The divisor for the operation. Must be a positive data size unit.
- * @throws IllegalArgumentException if [other] is equal to zero.
- */
- public operator fun div(other: DataSize): Double {
- require(other.rawValue > 0) {
- "data size unit $other must be a positive value to perform division"
- }
-
- val coarserUnit = maxOf(this.sizeUnit, other.sizeUnit)
- return this.toDouble(coarserUnit) / other.toDouble(coarserUnit)
+ val result = rawValue.divide(BigDecimal(scale, UnitMathContext), DivisionMathContext)
+ return normalizedDataSizeOf(result, unit)
}
public operator fun times(scale: Int): DataSize {
- if (scale == 0) return Zero
+ if (scale == 0) return if (isBinary()) binary.Zero else decimal.Zero
- val result = rawValue * scale
- return dataSizeOfNormalized(value = result)
+ val result = rawValue.multiply(BigDecimal(scale, UnitMathContext), UnitMathContext)
+ return normalizedDataSizeOf(result, unit)
}
public operator fun times(scale: Double): DataSize {
@@ -195,201 +174,165 @@ public value class DataSize internal constructor(
return times(intScale)
}
- val unit = sizeUnit
- val result = toDouble(unit) * scale
- return result.toDataSize(unit = unit)
+ val result = rawValue.multiply(BigDecimal(scale, UnitMathContext), UnitMathContext)
+ return normalizedDataSizeOf(result, unit)
}
public operator fun plus(other: DataSize): DataSize {
val result = rawValue + other.rawValue
- return dataSizeOfNormalized(value = result)
+ return normalizedDataSizeOf(result, unit)
}
public operator fun minus(other: DataSize): DataSize {
val result = rawValue - other.rawValue
- return dataSizeOfNormalized(value = result)
+ return normalizedDataSizeOf(result, unit)
}
- /** Returns true, if the data size value is infinite. */
- public fun isInfinite(): Boolean = rawValue == Infinite.rawValue
+ /** Returns `true` if this value represents the maximum supported size. */
+ public fun isInfinite(): Boolean =
+ if (isBinary()) rawValue == binary.Infinite.rawValue else rawValue == decimal.Infinite.rawValue
+
+ /** Returns `true` if this value is equal to zero bytes. */
+ public fun isZero(): Boolean =
+ if (isBinary()) rawValue == binary.Zero.rawValue else rawValue == decimal.Zero.rawValue
+
+ /** Returns `true` if this instance uses a decimal (SI) unit. */
+ public fun isDecimal(): Boolean = unit.isDecimalUnit()
- /** Returns true, if the data size value is zero. */
- public fun isZero(): Boolean = rawValue == Zero.rawValue
+ /** Returns `true` if this instance uses a binary (IEC) unit. */
+ public fun isBinary(): Boolean = unit.isBinaryUnit()
- override fun compareTo(other: DataSize): Int =
- this.rawValue.compareTo(other.rawValue)
+ /** Compares this value with another [DataSize] based on byte magnitude. */
+ override fun compareTo(other: DataSize): Int = this.rawValue.compareTo(other.rawValue)
/**
- * Returns the value of this data size expressed as a [Double] number of the specified [DataSizeUnit].
- * The operation may involve rounding when the result cannot be represented exactly with a [Double] number.
+ * Returns this value expressed as a [Double] in the specified [unit].
+ * Precision may be lost for large values due to floating-point representation.
*/
public fun toDouble(unit: DataSizeUnit): Double =
convertDataSizeUnit(
- value = rawValue.coerceIn(0, MAX_SIZE).toDouble(),
- sourceUnit = sizeUnit,
+ value = rawValue,
+ sourceUnit = this.unit,
targetUnit = unit,
- )
+ ).stripTrailingZeros().toDouble()
/**
- * Returns the value of this data size expressed as a [Int] number of the specified [DataSizeUnit].
- * If the result doesn't fit in the range of [Int] type, it is coerced into that range.
+ * Returns this value expressed as an [Int] in the specified [unit].
+ * Values outside the [Int] range are coerced.
*/
public fun toInt(unit: DataSizeUnit): Int =
convertDataSizeUnit(
- value = rawValue.coerceIn(0, MAX_SIZE),
- sourceUnit = sizeUnit,
+ value = rawValue,
+ sourceUnit = this.unit,
targetUnit = unit,
- ).toInt()
+ ).stripTrailingZeros().toInt()
- /** Returns the value of this data size expressed as a [Long] number of the specified [DataSizeUnit]. */
+ /** Returns this value expressed as a [Long] in the specified [unit]. */
public fun toLong(unit: DataSizeUnit): Long =
convertDataSizeUnit(
- value = rawValue.coerceIn(0, MAX_SIZE),
- sourceUnit = sizeUnit,
+ value = rawValue,
+ sourceUnit = this.unit,
targetUnit = unit,
- )
+ ).stripTrailingZeros().toLong()
- /** The value of this [DataSize] expressed as a [Double] number of terabytes. */
+ /** Returns this value expressed in pebibytes. */
+ public val inPebibytes: Double
+ get() = toDouble(BinaryUnit.Pebibyte)
+
+ /** Returns this value expressed in petabytes. */
+ public val inPetabytes: Double
+ get() = toDouble(DecimalUnit.Petabyte)
+
+ /** Returns this value expressed in tebibytes. */
+ public val inTebibytes: Double
+ get() = toDouble(BinaryUnit.Tebibyte)
+
+ /** Returns this value expressed in terabytes. */
public val inTerabytes: Double
- get() = toDouble(unit = DataSizeUnit.Terabytes)
+ get() = toDouble(DecimalUnit.Terabyte)
- /** The value of this [DataSize] expressed as a [Double] number of gigabytes. */
+ /** Returns this value expressed in gibibytes. */
+ public val inGibibytes: Double
+ get() = toDouble(BinaryUnit.Gibibyte)
+
+ /** Returns this value expressed in gigabytes. */
public val inGigabytes: Double
- get() = toDouble(unit = DataSizeUnit.Gigabytes)
+ get() = toDouble(DecimalUnit.Gigabyte)
+
+ /** Returns this value expressed in mebibytes. */
+ public val inMebibytes: Double
+ get() = toDouble(BinaryUnit.Mebibyte)
- /** The value of this [DataSize] expressed as a [Double] number of megabytes. */
+ /** Returns this value expressed in megabytes. */
public val inMegabytes: Double
- get() = toDouble(unit = DataSizeUnit.Megabytes)
+ get() = toDouble(DecimalUnit.Megabyte)
- /** The value of this [DataSize] expressed as a [Double] number of kilobytes. */
+ /** Returns this value expressed in kibibytes. */
+ public val inKibibytes: Double
+ get() = toDouble(BinaryUnit.Kibibyte)
+
+ /** Returns this value expressed in kilobytes. */
public val inKilobytes: Double
- get() = toDouble(unit = DataSizeUnit.Kilobytes)
+ get() = toDouble(DecimalUnit.Kilobyte)
- /** The value of this [DataSize] expressed as a [Long] number of bytes. */
+ /** Returns this value expressed in bytes. */
public val inBytes: Long
- get() = toLong(unit = DataSizeUnit.Bytes)
+ get() = toLong(if (isBinary()) BinaryUnit.Byte else DecimalUnit.Byte)
/**
- * Returns a string representation of this data size value expressed in the given [unit]
- * and formatted with the specified [decimals] number of digits after decimal point.
- *
- * Special case:
- * - an infinite data size is formatted as `"Infinity"` without a unit.
+ * Returns a string representation of this value in bytes.
*
- * @param decimals the number of digits after decimal point to show. The value must be non-negative.
- * No more than 2 decimals will be shown, even if a larger number is requested.
- * @return the value of data size in the specified [unit] followed by that unit abbreviated name: `B`, `KB`, `MB`, `GB`, or `TB`.
- * @throws IllegalArgumentException if [decimals] is less than zero.
+ * - Returns `"Infinity"` for infinite values.
+ * - Otherwise returns the byte value without unit suffix.
*/
- public fun toString(unit: DataSizeUnit, decimals: Int = 0): String {
- require(decimals >= 0) { "decimals must not be negative, but was $decimals" }
-
- val number = toDouble(unit)
- if (number.isInfinite()) return number.toString()
-
- return "${createFormatForDecimals(number, decimals.coerceAtMost(2)).format(number)} ${unit.shortName()}"
+ override fun toString(): String = when {
+ isInfinite() -> "Infinity"
+ isBinary() -> toLong(BinaryUnit.Byte).toString()
+ isDecimal() -> toLong(DecimalUnit.Byte).toString()
+ else -> "0"
}
/**
- * Returns a string representation of this data size value in decimal base expressed in the given [unit]
- * and formatted with the specified [decimals] number of digits after decimal point.
+ * Returns a formatted string representation of this value.
+ *
+ * The value is converted to the specified [unit] and formatted using [formatter].
*
* Special case:
- * - an infinite data size is formatted as `"Infinity"` without a unit.
+ * - Infinite values are formatted as `"Infinity"` without a unit.
*
- * @param decimals the number of digits after decimal point to show. The value must be non-negative.
- * No more than 2 decimals will be shown, even if a larger number is requested.
- * @return the value of data size in the specified [unit] followed by that unit abbreviated name: `B`, `KB`, `MB`, `GB`, or `TB`.
- * @throws IllegalArgumentException if [decimals] is less than zero.
+ * @param unit the unit to express the value in.
+ * @param fractionDigits number of digits after the decimal point (must be non-negative).
+ * @param formatter formatting strategy used to produce the output.
+ *
+ * @throws IllegalArgumentException if [fractionDigits] is negative.
*/
- @ExperimentalDataSizeApi
- public fun toDecimalString(unit: DataSizeUnit, decimals: Int = 0): String {
- require(decimals >= 0) { "decimals must be not negative, but was $decimals" }
-
- val number = toDecimalUnit(toDouble(unit), unit)
- if (number.isInfinite()) return number.toString()
-
- return "${createFormatForDecimals(number, decimals.coerceAtMost(2)).format(number)} ${unit.shortName()}"
- }
-
- private fun createFormatForDecimals(number: Double, decimals: Int) =
- DecimalFormat("0").apply {
- if (decimals > 0) maximumFractionDigits = decimals
- roundingMode = RoundingMode.HALF_UP
- decimalFormatSymbols = DecimalFormatSymbols().apply {
- decimalSeparator = ','
- groupingSeparator = '.'
- isGroupingUsed = number >= 10000
- groupingSize = 3
- }
- }
+ public fun toString(
+ unit: DataSizeUnit,
+ fractionDigits: Int = 0,
+ formatter: DataSizeFormatter = DefaultDataSizeFormatter(DefaultDataSizeFormatter.createFormat()),
+ ): String = formatter.format(this, unit, fractionDigits)
}
-/**
- * Returns [DataSize] converted from a double with [DataSizeUnit] type.
- *
- * @throws IllegalArgumentException value is Not-a-Number(NaN) or infinite.
- * @throws IllegalStateException value is not within 0 <= value <= [MAX_SIZE]
- */
-public fun Double.toDataSize(unit: DataSizeUnit): DataSize {
- require(!this.isNaN() && !this.isInfinite()) { "DataSizeUnit value cannot be NaN or Infinite." }
+// Maximum representable value (~9 exabytes).
+internal val MAX_SIZE: BigDecimal = BigDecimal(Long.MAX_VALUE, UnitMathContext)
- val value = convertDataSizeUnit(
- value = this,
- sourceUnit = unit,
- targetUnit = DataSizeUnit.Bytes,
- ).roundToLong()
+private fun normalizedDataSizeOf(value: BigDecimal, unit: DataSizeUnit): DataSize =
+ DataSize(value.coerceIn(BigDecimal.ZERO, MAX_SIZE), unit)
- return DataSize(value)
-}
-/**
- * Returns [DataSize] converted from an integer with [DataSizeUnit] type.
- *
- * @throws IllegalStateException value is not within 0 <= value <= [MAX_SIZE]
- */
-public fun Int.toDataSize(unit: DataSizeUnit): DataSize {
- val value = convertDataSizeUnit(
- value = this.toLong(),
- sourceUnit = unit,
- targetUnit = DataSizeUnit.Bytes,
- )
-
- return DataSize(value)
-}
+/** Returns the larger of two [DataSize] values (byte-based comparison). */
+public fun max(a: DataSize, b: DataSize): DataSize = if (a.rawValue >= b.rawValue) a else b
-/**
- * Returns [DataSize] converted from a long with [DataSizeUnit] type.
- *
- * @throws IllegalStateException value is not within 0 <= value <= [MAX_SIZE]
- */
-public fun Long.toDataSize(unit: DataSizeUnit): DataSize {
- val value = convertDataSizeUnit(
- value = this,
- sourceUnit = unit,
- targetUnit = DataSizeUnit.Bytes,
- )
-
- return DataSize(value)
-}
-
-public fun max(a: DataSize, b: DataSize): DataSize =
- dataSizeOf(max(a.inBytes, b.inBytes))
+/** Returns the smaller of two [DataSize] values (byte-based comparison). */
+public fun min(a: DataSize, b: DataSize): DataSize = if (a.rawValue <= b.rawValue) a else b
-public fun min(a: DataSize, b: DataSize): DataSize =
- dataSizeOf(min(a.inBytes, b.inBytes))
-
-// Max size of 8 exabytes should be enough.
-internal const val MAX_SIZE = Long.MAX_VALUE
-
-private fun dataSizeOfNormalized(value: Long) =
- dataSizeOf(value.coerceIn(0, MAX_SIZE))
-
-private fun dataSizeOf(value: Long) = DataSize(value)
+/** Returns value or binary zero if null.*/
+@Suppress("NOTHING_TO_INLINE")
+public inline fun DataSize?.orBinaryZero(): DataSize = this ?: DataSize.binary.Zero
-/** Returns the specified [DataSize] if not `null`, or [DataSize.Zero] otherwise. */
+/** Returns value or decimal zero if null.*/
@Suppress("NOTHING_TO_INLINE")
-public inline fun DataSize?.orZero(): DataSize = this ?: DataSize.Zero
+public inline fun DataSize?.orDecimalZero(): DataSize = this ?: DataSize.decimal.Zero
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSizeFormatter.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSizeFormatter.kt
index fb24734..417edae 100644
--- a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSizeFormatter.kt
+++ b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSizeFormatter.kt
@@ -5,65 +5,36 @@
*/
package io.github.ardiien.datasize
-import io.github.ardiien.datasize.DataSize.Companion.bytes
+import io.github.ardiien.datasize.unit.DataSizeUnit
-/** A utility class for formatting data size values in a human-readable form. */
-public object DataSizeFormatter {
+/** Provides utilities for formatting [DataSize] values into human-readable strings. */
+public interface DataSizeFormatter {
/**
- * Returns [DataSizeUnit] from [value] depending on the threshold.
+ * Returns the most appropriate [DataSizeUnit] for the given [value] based on its magnitude.
+ * The selected unit is the largest unit whose byte value does not exceed the given [value].
*
- * @throws IllegalStateException value is not within 0 <= value <= [MAX_SIZE]
+ * @throws IllegalStateException if the value is not within the supported range
*/
- @JvmSynthetic
- public fun unitFrom(value: DataSize): DataSizeUnit =
- when {
- value.inBytes > TERA_SCALE -> DataSizeUnit.Terabytes
- value.inBytes > GIGA_SCALE -> DataSizeUnit.Gigabytes
- value.inBytes > MEGA_SCALE -> DataSizeUnit.Megabytes
- value.inBytes > KILO_SCALE -> DataSizeUnit.Kilobytes
- else -> DataSizeUnit.Bytes
- }
+ public fun unitFrom(value: DataSize): DataSizeUnit
/**
- * Returns a string representation of this data size [value] expressed in the given [unit]
- * and formatted with the specified [decimals] number of digits after decimal point.
+ * Returns a string representation of the given [value] expressed in the specified [unit]
+ * and formatted with the given number of fractional [fractionDigits].
*
- * @return String representation of [value] in a custom format.
- * @throws IllegalStateException value is not within 0 <= value <= [MAX_SIZE]
- * @throws IllegalArgumentException if [decimals] is less than zero.
- * @see DataSize.toString
- */
- @JvmSynthetic
- public fun format(
- value: DataSize,
- unit: DataSizeUnit = unitFrom(value),
- decimals: Int = 0,
- ): String = value.toString(unit, decimals)
-
- /**
- * Java-use only.
- * For Kotlin, use unitFrom(value: DataSize) instead.
+ * If [unit] is not provided, a suitable unit is selected automatically using [unitFrom].
*
- * @return [DataSizeUnit] from [value] depending on the threshold.
- * @throws IllegalStateException value is not within 0 <= value <= [MAX_SIZE]
- */
- @JvmStatic
- @Throws(IllegalStateException::class)
- public fun unitFrom(value: Long): DataSizeUnit = unitFrom(value.bytes)
-
- /**
- * Java-use only.
- * For Kotlin, use format(value: DataSize, unit: DataSizeUnit, decimals: Int) instead.
+ * @param value the data size to format.
+ * @param unit the unit to express the value in.
+ * @param fractionDigits the number of digits to display after the decimal point.
*
- * @return String representation of [value] in a custom format.
+ * @throws IllegalStateException if the value is not within the supported range.
+ * @throws IllegalArgumentException if [fractionDigits] is negative.
*/
- @JvmStatic
- @Throws(IllegalStateException::class)
public fun format(
- value: Long,
+ value: DataSize,
unit: DataSizeUnit = unitFrom(value),
- decimals: Int = 0,
- ): String = format(value.bytes, unit, decimals)
+ fractionDigits: Int = 0,
+ ): String
}
\ No newline at end of file
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSizeUnit.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSizeUnit.kt
deleted file mode 100644
index 744ed33..0000000
--- a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/DataSizeUnit.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2026 ardiien
- * Licensed under the Apache License, Version 2.0.
- * See http://www.apache.org/licenses/LICENSE-2.0
- */
-package io.github.ardiien.datasize
-
-import kotlin.math.floor
-import kotlin.math.pow
-
-
-internal const val BINARY_BASE: Long = 1024
-internal const val DECIMAL_BASE: Long = 1000
-
-internal const val BYTE_SCALE: Long = 1
-internal const val KILO_SCALE: Long = BYTE_SCALE * BINARY_BASE
-internal const val MEGA_SCALE: Long = KILO_SCALE * BINARY_BASE
-internal const val GIGA_SCALE: Long = MEGA_SCALE * BINARY_BASE
-internal const val TERA_SCALE: Long = GIGA_SCALE * BINARY_BASE
-
-
-/**
- * The list of possible size measurement units, in which a data size can be expressed.
- * The smallest time unit is [Bytes] and the largest is [Terabytes].
- */
-public enum class DataSizeUnit(private val scale: Long) {
- Terabytes(TERA_SCALE),
- Gigabytes(GIGA_SCALE),
- Megabytes(MEGA_SCALE),
- Kilobytes(KILO_SCALE),
- Bytes(BYTE_SCALE);
-
-
- /** Converts the given size [value] of the current [scale] into the specified [unit]. */
- internal fun convert(value: Long, unit: DataSizeUnit): Long =
- cvt(value, this.scale, unit.scale)
-
- internal companion object {
- private fun cvt(v: Long, src: Long, dst: Long): Long {
- return if (src == dst) v
- else floor((v / (dst / src.toDouble()))).toLong()
- }
- }
-}
-
-@ExperimentalDataSizeApi
-public fun toDecimalUnit(
- value: Double,
- unit: DataSizeUnit,
-): Double {
- val power = unit.base()
- val binaryBase = BINARY_BASE.toDouble().pow(power)
- val decimalBase = DECIMAL_BASE.toDouble().pow(power)
-
- return value * (binaryBase / decimalBase)
-}
-
-@ExperimentalDataSizeApi
-public fun toDecimalUnit(
- value: Int,
- unit: DataSizeUnit,
-): Double = toDecimalUnit(value.toDouble(), unit)
-
-public fun toBinaryUnit(
- value: Double,
- unit: DataSizeUnit,
-): Double {
- val power = unit.base()
- val binaryBase = BINARY_BASE.toDouble().pow(power)
- val decimalBase = DECIMAL_BASE.toDouble().pow(power)
-
- return value * (decimalBase / binaryBase)
-}
-
-public fun toBinaryUnit(
- value: Int,
- unit: DataSizeUnit,
-): Double = toBinaryUnit(value.toDouble(), unit)
-
-/** Converts the given size [value] expressed in the specified [sourceUnit] into the specified [targetUnit] ar [Long]. */
-internal fun convertDataSizeUnit(
- value: Long,
- sourceUnit: DataSizeUnit,
- targetUnit: DataSizeUnit,
-): Long = sourceUnit.convert(value, targetUnit)
-
-/** Converts the given size [value] expressed in the specified [sourceUnit] into the specified [targetUnit] ar [Double]. */
-internal fun convertDataSizeUnit(
- value: Double,
- sourceUnit: DataSizeUnit,
- targetUnit: DataSizeUnit,
-): Double {
- if (sourceUnit == targetUnit) return value
-
- val sourceInTargets = targetUnit.convert(1, sourceUnit)
- if (sourceInTargets > 0)
- return (value / sourceInTargets)
-
- val otherInThis = sourceUnit.convert(1, targetUnit)
- return (value * otherInThis)
-}
-
-internal fun DataSizeUnit.shortName(): String =
- when (this) {
- DataSizeUnit.Terabytes -> "TB"
- DataSizeUnit.Gigabytes -> "GB"
- DataSizeUnit.Megabytes -> "MB"
- DataSizeUnit.Kilobytes -> "KB"
- DataSizeUnit.Bytes -> "B"
- }
-
-internal fun DataSizeUnit.base(): Int =
- when (this) {
- DataSizeUnit.Terabytes -> 4
- DataSizeUnit.Gigabytes -> 3
- DataSizeUnit.Megabytes -> 2
- DataSizeUnit.Kilobytes -> 1
- DataSizeUnit.Bytes -> 0
- }
\ No newline at end of file
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/BinaryArrayDataSizeBuilder.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/BinaryArrayDataSizeBuilder.kt
new file mode 100644
index 0000000..4177671
--- /dev/null
+++ b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/BinaryArrayDataSizeBuilder.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2026 ardiien
+ * Licensed under the Apache License, Version 2.0.
+ * See http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.github.ardiien.datasize.builder
+
+import io.github.ardiien.datasize.DataSize
+import io.github.ardiien.datasize.ExperimentalDataSizeApi
+import io.github.ardiien.datasize.unit.BinaryUnit
+import io.github.ardiien.datasize.unit.convertDataSizeUnit
+
+
+@ExperimentalDataSizeApi
+public class BinaryArrayDataSizeBuilder(private val number: Number) {
+
+ init {
+ check(number is Int) {
+ "DataSize must be constructed only with Int."
+ }
+ }
+
+ /** Returns a [DataSize] representing this value in bytes. */
+ public inline val bytes: DataSize
+ get() = toDataSize(unit = BinaryUnit.Byte)
+
+ /**
+ * Converts this numeric value expressed in the given binary [unit] into a [DataSize].
+ * The resulting [DataSize] is normalized to bytes.
+ *
+ * @throws IllegalStateException if the resulting value is not within the supported range.
+ */
+ public fun toDataSize(unit: BinaryUnit): DataSize {
+ val targetUnit = BinaryUnit.Byte
+ val value = convertDataSizeUnit(
+ value = number,
+ sourceUnit = unit,
+ targetUnit = targetUnit,
+ )
+
+ return DataSize(value, targetUnit)
+ }
+}
\ No newline at end of file
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/BinaryNumberDataSizeBuilder.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/BinaryNumberDataSizeBuilder.kt
new file mode 100644
index 0000000..b4dc57e
--- /dev/null
+++ b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/BinaryNumberDataSizeBuilder.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2026 ardiien
+ * Licensed under the Apache License, Version 2.0.
+ * See http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.github.ardiien.datasize.builder
+
+import io.github.ardiien.datasize.DataSize
+import io.github.ardiien.datasize.ExperimentalDataSizeApi
+import io.github.ardiien.datasize.unit.BinaryUnit
+import io.github.ardiien.datasize.unit.convertDataSizeUnit
+
+
+@ExperimentalDataSizeApi
+public class BinaryNumberDataSizeBuilder(private val number: Number) {
+
+ init {
+ check(number is Int || number is Long || number is Double) {
+ "DataSize must be constructed only with Int, Long, or Double."
+ }
+ }
+
+ /** Returns a [DataSize] representing this value in pebibytes (PiB). */
+ public inline val pebibytes: DataSize
+ get() = toDataSize(unit = BinaryUnit.Pebibyte)
+
+ /** Returns a [DataSize] representing this value in tebibytes (TiB). */
+ public inline val tebibytes: DataSize
+ get() = toDataSize(unit = BinaryUnit.Tebibyte)
+
+ /** Returns a [DataSize] representing this value in gibibytes (GiB). */
+ public inline val gibibytes: DataSize
+ get() = toDataSize(unit = BinaryUnit.Gibibyte)
+
+ /** Returns a [DataSize] representing this value in mebibytes (MiB). */
+ public inline val mebibytes: DataSize
+ get() = toDataSize(unit = BinaryUnit.Mebibyte)
+
+ /** Returns a [DataSize] representing this value in kibibytes (KiB). */
+ public inline val kibibytes: DataSize
+ get() = toDataSize(unit = BinaryUnit.Kibibyte)
+
+ /** Returns a [DataSize] representing this value in bytes. */
+ public inline val bytes: DataSize
+ get() = toDataSize(unit = BinaryUnit.Byte)
+
+ /**
+ * Converts this numeric value expressed in the given binary [unit] into a [DataSize].
+ * The resulting [DataSize] is normalized to bytes.
+ *
+ * @throws IllegalStateException if the resulting value is not within the supported range.
+ */
+ public fun toDataSize(unit: BinaryUnit): DataSize {
+ val targetUnit = BinaryUnit.Byte
+ val value = convertDataSizeUnit(
+ value = number,
+ sourceUnit = unit,
+ targetUnit = targetUnit,
+ )
+
+ return DataSize(value, targetUnit)
+ }
+}
\ No newline at end of file
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/DecimalArrayDataSizeBuilder.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/DecimalArrayDataSizeBuilder.kt
new file mode 100644
index 0000000..8fd712c
--- /dev/null
+++ b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/DecimalArrayDataSizeBuilder.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2026 ardiien
+ * Licensed under the Apache License, Version 2.0.
+ * See http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.github.ardiien.datasize.builder
+
+import io.github.ardiien.datasize.DataSize
+import io.github.ardiien.datasize.ExperimentalDataSizeApi
+import io.github.ardiien.datasize.unit.DecimalUnit
+import io.github.ardiien.datasize.unit.convertDataSizeUnit
+
+
+@ExperimentalDataSizeApi
+public class DecimalArrayDataSizeBuilder(private val number: Number) {
+
+ init {
+ check(number is Int) {
+ "DataSize must be constructed only with Int."
+ }
+ }
+
+ /** Returns a [DataSize] representing this value in bytes. */
+ public inline val bytes: DataSize
+ get() = toDataSize(unit = DecimalUnit.Byte)
+
+ /**
+ * Converts this numeric value expressed in the given decimal [unit] into a [DataSize].
+ * The resulting [DataSize] is normalized to bytes.
+ *
+ * @throws IllegalStateException if the resulting value is not within the supported range.
+ */
+ public fun toDataSize(unit: DecimalUnit): DataSize {
+ val targetUnit = DecimalUnit.Byte
+ val value = convertDataSizeUnit(
+ value = number,
+ sourceUnit = unit,
+ targetUnit = targetUnit,
+ )
+
+ return DataSize(value, targetUnit)
+ }
+}
\ No newline at end of file
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/DecimalNumberDataSizeBuilder.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/DecimalNumberDataSizeBuilder.kt
new file mode 100644
index 0000000..9d533d5
--- /dev/null
+++ b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/builder/DecimalNumberDataSizeBuilder.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2026 ardiien
+ * Licensed under the Apache License, Version 2.0.
+ * See http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.github.ardiien.datasize.builder
+
+import io.github.ardiien.datasize.DataSize
+import io.github.ardiien.datasize.ExperimentalDataSizeApi
+import io.github.ardiien.datasize.unit.DecimalUnit
+import io.github.ardiien.datasize.unit.convertDataSizeUnit
+
+
+@ExperimentalDataSizeApi
+public class DecimalNumberDataSizeBuilder(private val number: Number) {
+
+ init {
+ check(number is Int || number is Long || number is Double) {
+ "DataSize must be constructed only with Int, Long, or Double."
+ }
+ }
+
+ /** Returns a [DataSize] representing this value in petabytes (PB). */
+ public inline val petabytes: DataSize
+ get() = toDataSize(unit = DecimalUnit.Petabyte)
+
+ /** Returns a [DataSize] representing this value in terabytes (TB). */
+ public inline val terabytes: DataSize
+ get() = toDataSize(unit = DecimalUnit.Terabyte)
+
+ /** Returns a [DataSize] representing this value in gigabytes (GB). */
+ public inline val gigabytes: DataSize
+ get() = toDataSize(unit = DecimalUnit.Gigabyte)
+
+ /** Returns a [DataSize] representing this value in megabytes (MB). */
+ public inline val megabytes: DataSize
+ get() = toDataSize(unit = DecimalUnit.Megabyte)
+
+ /** Returns a [DataSize] representing this value in kilobytes (KB). */
+ public inline val kilobytes: DataSize
+ get() = toDataSize(unit = DecimalUnit.Kilobyte)
+
+ /** Returns a [DataSize] representing this value in bytes. */
+ public inline val bytes: DataSize
+ get() = toDataSize(unit = DecimalUnit.Byte)
+
+ /**
+ * Converts this numeric value expressed in the given decimal [unit] into a [DataSize].
+ * The resulting [DataSize] is normalized to bytes.
+ *
+ * @throws IllegalStateException if the resulting value is not within the supported range.
+ */
+ public fun toDataSize(unit: DecimalUnit): DataSize {
+ val targetUnit = DecimalUnit.Byte
+ val value = convertDataSizeUnit(
+ value = number,
+ sourceUnit = unit,
+ targetUnit = targetUnit,
+ )
+
+ return DataSize(value, targetUnit)
+ }
+}
\ No newline at end of file
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/formatter/DefaultDataSizeFormatter.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/formatter/DefaultDataSizeFormatter.kt
new file mode 100644
index 0000000..1c1448e
--- /dev/null
+++ b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/formatter/DefaultDataSizeFormatter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2026 ardiien
+ * Licensed under the Apache License, Version 2.0.
+ * See http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.github.ardiien.datasize.formatter
+
+import io.github.ardiien.datasize.DataSize
+import io.github.ardiien.datasize.DataSizeFormatter
+import io.github.ardiien.datasize.unit.DataSizeUnit
+import java.math.RoundingMode
+import java.text.DecimalFormat
+import java.text.DecimalFormatSymbols
+
+
+public class DefaultDataSizeFormatter(private val formatter: DecimalFormat) : DataSizeFormatter {
+
+ /**
+ * Returns the most appropriate [DataSizeUnit] for the given [value] based on its magnitude.
+ * The selected unit is the largest unit whose byte value does not exceed the given [value].
+ *
+ * @throws IllegalStateException if the value is not within the supported range
+ */
+ public override fun unitFrom(value: DataSize): DataSizeUnit {
+ val units = value.unit.entries()
+ return units.firstOrNull { value.rawValue > it.value() } ?: units.last()
+ }
+
+ /**
+ * Returns a string representation of the given [value] expressed in the specified [unit]
+ * and formatted with the given number of fractional [fractionDigits].
+ *
+ * If [unit] is not provided, a suitable unit is selected automatically using [unitFrom].
+ *
+ * @param value the data size to format.
+ * @param unit the unit to express the value in.
+ * @param fractionDigits the number of digits to display after the decimal point.
+ *
+ * @throws IllegalStateException if the value is not within the supported range.
+ * @throws IllegalArgumentException if [fractionDigits] is negative.
+ */
+ public override fun format(
+ value: DataSize,
+ unit: DataSizeUnit,
+ fractionDigits: Int,
+ ): String {
+ require(fractionDigits >= 0) { "fractionDigits must not be negative, but was $fractionDigits" }
+
+ val number = value.toDouble(unit)
+ if (number.isInfinite()) return number.toString()
+
+ val actualFormatter = extendFormat(formatter, number, fractionDigits)
+ return "${actualFormatter.format(number)} ${unit.abbreviation()}"
+ }
+
+ public companion object {
+
+ public fun createFormat(
+ roundingMode: RoundingMode = RoundingMode.HALF_UP,
+ groupingSize: Int = 3,
+ isGroupingUsed: Boolean = true,
+ decimalFormatSymbols: DecimalFormatSymbols = createFormatSymbols(),
+ ): DecimalFormat = DecimalFormat("0")
+ .apply {
+ this.roundingMode = roundingMode
+ this.isGroupingUsed = isGroupingUsed
+ this.groupingSize = groupingSize
+ this.decimalFormatSymbols = decimalFormatSymbols
+ }
+
+ internal fun extendFormat(formatter: DecimalFormat, number: Double, fractionDigits: Int): DecimalFormat =
+ formatter.apply {
+ maximumFractionDigits = fractionDigits.coerceAtMost(2)
+ isGroupingUsed = number > 9999.999999999
+ }
+
+ public fun createFormatSymbols(
+ decimalSeparator: Char = ',',
+ groupingSeparator: Char = ' ',
+ ): DecimalFormatSymbols = DecimalFormatSymbols()
+ .apply {
+ this.decimalSeparator = decimalSeparator
+ this.groupingSeparator = groupingSeparator
+ }
+ }
+}
\ No newline at end of file
diff --git a/datasize-core/src/main/kotlin/io/github/ardiien/datasize/unit/DataSizeUnit.kt b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/unit/DataSizeUnit.kt
new file mode 100644
index 0000000..6f2c3a1
--- /dev/null
+++ b/datasize-core/src/main/kotlin/io/github/ardiien/datasize/unit/DataSizeUnit.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2026 ardiien
+ * Licensed under the Apache License, Version 2.0.
+ * See http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.github.ardiien.datasize.unit
+
+import io.github.ardiien.datasize.ExperimentalDataSizeApi
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+import java.math.BigDecimal
+import java.math.MathContext
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+
+/** Base used for decimal units (powers of 10). */
+private const val SI_BASE: Double = 10.0
+
+/** Base used for binary units (powers of 2). */
+private const val IEC_BASE: Double = 2.0
+
+
+/**
+ * Represents a unit of data size.
+ *
+ * Each unit defines a fixed number of bytes and belongs to a specific unit system
+ * (decimal or binary). Instances of this class are immutable and provide a
+ * type-safe way to work with data size units.
+ *
+ * @see Binary prefix
+ * @see File size
+ */
+@ExperimentalDataSizeApi
+public abstract class DataSizeUnit internal constructor(
+ private val name: String,
+ private val abbreviation: String,
+ base: Double,
+ exponent: Int,
+) {
+ private val value: BigDecimal = base.toBigDecimal().pow(exponent)
+
+ /** Returns the full human-readable name of this unit. */
+ public fun name(): String = name
+
+ /** Returns the abbreviated symbol of this unit. */
+ public fun abbreviation(): String = abbreviation
+
+ /** Returns the exact number of bytes represented by a single unit. */
+ public fun value(): BigDecimal = value
+
+ /** Returns all units belonging to the same unit system, ordered from largest to smallest. */
+ public open fun entries(): ImmutableList = persistentListOf()
+}
+
+/** Returns `true` if this unit belongs to the decimal (SI) system. */
+@ExperimentalDataSizeApi
+@OptIn(ExperimentalContracts::class)
+public fun DataSizeUnit.isDecimalUnit(): Boolean {
+ contract { returns(true) implies (this@isDecimalUnit is DecimalUnit) }
+ return this is DecimalUnit
+}
+
+/** Returns `true` if this unit belongs to the binary (IEC) system. */
+@ExperimentalDataSizeApi
+@OptIn(ExperimentalContracts::class)
+public fun DataSizeUnit.isBinaryUnit(): Boolean {
+ contract { returns(true) implies (this@isBinaryUnit is BinaryUnit) }
+ return this is BinaryUnit
+}
+
+/**
+ * Represents decimal (SI) data size units based on powers of 10.
+ * Units range from [Byte] (10⁰) to [Petabyte] (10¹⁵).
+ */
+@ExperimentalDataSizeApi
+public sealed class DecimalUnit(
+ name: String,
+ abbreviation: String,
+ base: Double,
+ exponent: Int,
+) : DataSizeUnit(name, abbreviation, base, exponent) {
+
+ public data object Byte : DecimalUnit(name = "bytes", abbreviation = "B", base = SI_BASE, exponent = 0)
+ public data object Kilobyte : DecimalUnit(name = "kilobytes", abbreviation = "KB", base = SI_BASE, exponent = 3)
+ public data object Megabyte : DecimalUnit(name = "megabytes", abbreviation = "MB", base = SI_BASE, exponent = 6)
+ public data object Gigabyte : DecimalUnit(name = "gigabytes", abbreviation = "GB", base = SI_BASE, exponent = 9)
+ public data object Terabyte : DecimalUnit(name = "terabytes", abbreviation = "TB", base = SI_BASE, exponent = 12)
+ public data object Petabyte : DecimalUnit(name = "petabytes", abbreviation = "PB", base = SI_BASE, exponent = 15)
+
+ public override fun entries(): ImmutableList = decimalEntries
+}
+
+private val decimalEntries: ImmutableList = persistentListOf(
+ DecimalUnit.Petabyte,
+ DecimalUnit.Terabyte,
+ DecimalUnit.Gigabyte,
+ DecimalUnit.Megabyte,
+ DecimalUnit.Kilobyte,
+ DecimalUnit.Byte,
+)
+
+/**
+ * Represents binary (IEC) data size units based on powers of 2.
+ * Units range from [Byte] (2⁰) to [Pebibyte] (2⁵⁰).
+ *
+ * Note: For consistency with common user expectations, unit names and abbreviations
+ * use decimal-style labels (e.g., "KB", "MB"), even though the underlying values follow the binary system.
+ */
+@ExperimentalDataSizeApi
+public sealed class BinaryUnit(
+ name: String,
+ abbreviation: String,
+ base: Double,
+ exponent: Int,
+) : DataSizeUnit(name, abbreviation, base, exponent) {
+
+ public data object Byte : BinaryUnit(name = "bytes", abbreviation = "B", base = IEC_BASE, exponent = 0)
+ public data object Kibibyte : BinaryUnit(name = "kilobytes", abbreviation = "KB", base = IEC_BASE, exponent = 10)
+ public data object Mebibyte : BinaryUnit(name = "megabytes", abbreviation = "MB", base = IEC_BASE, exponent = 20)
+ public data object Gibibyte : BinaryUnit(name = "gigabytes", abbreviation = "GB", base = IEC_BASE, exponent = 30)
+ public data object Tebibyte : BinaryUnit(name = "terabytes", abbreviation = "TB", base = IEC_BASE, exponent = 40)
+ public data object Pebibyte : BinaryUnit(name = "petabytes", abbreviation = "PB", base = IEC_BASE, exponent = 50)
+
+ public override fun entries(): ImmutableList = binaryEntries
+}
+
+private val binaryEntries: ImmutableList = persistentListOf(
+ BinaryUnit.Pebibyte,
+ BinaryUnit.Tebibyte,
+ BinaryUnit.Gibibyte,
+ BinaryUnit.Mebibyte,
+ BinaryUnit.Kibibyte,
+ BinaryUnit.Byte,
+)
+
+internal fun convertDataSizeUnit(
+ value: Number,
+ sourceUnit: DataSizeUnit,
+ targetUnit: DataSizeUnit,
+): BigDecimal {
+ val value = when (value) {
+ is Int -> BigDecimal(value, UnitMathContext)
+ is Long -> BigDecimal(value, UnitMathContext)
+ is Double -> {
+ require(!(value.isNaN() || value.isInfinite())) { "DataSizeUnit value cannot be NaN or Infinite." }
+ BigDecimal(value, UnitMathContext)
+ }
+ else -> error("Unsupported number type ${value::class}")
+ }
+
+ if (sourceUnit == targetUnit) return value
+
+ return sourceUnit.value()
+ .divide(targetUnit.value(), DivisionMathContext)
+ .multiply(value, UnitMathContext)
+}
+
+internal fun convertDataSizeUnit(
+ value: BigDecimal,
+ sourceUnit: DataSizeUnit,
+ targetUnit: DataSizeUnit,
+): BigDecimal {
+ if (sourceUnit == targetUnit) return value
+
+ return sourceUnit.value()
+ .divide(targetUnit.value(), DivisionMathContext)
+ .multiply(value, UnitMathContext)
+}
+
+internal val UnitMathContext: MathContext = MathContext.UNLIMITED
+internal val DivisionMathContext: MathContext = MathContext.DECIMAL128
diff --git a/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DataSizeFormatterTest.kt b/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DataSizeFormatterTest.kt
deleted file mode 100644
index f09af41..0000000
--- a/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DataSizeFormatterTest.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2026 ardiien
- * Licensed under the Apache License, Version 2.0.
- * See http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package io.github.ardiien.datasize
-
-import io.github.ardiien.datasize.DataSize.Companion.bytes
-import io.github.ardiien.datasize.DataSize.Companion.gigabytes
-import io.github.ardiien.datasize.DataSize.Companion.kilobytes
-import io.github.ardiien.datasize.DataSize.Companion.megabytes
-import io.github.ardiien.datasize.DataSize.Companion.terabytes
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-
-class DataSizeFormatterTest {
-
- @Test
- fun `Bytes is returned as data size unit when expected`() {
- val expected = DataSizeUnit.Bytes
- val size = 500.bytes.inBytes
- val actualFromDataSize = DataSizeFormatter.unitFrom(size.bytes)
- val actualFromRawValue = DataSizeFormatter.unitFrom(size)
- assertEquals(expected, actualFromDataSize)
- assertEquals(expected, actualFromRawValue)
- }
-
- @Test
- fun `Kilobytes is returned as data size unit when expected`() {
- val expected = DataSizeUnit.Kilobytes
- val size = 500.kilobytes.inBytes
- val actualFromDataSize = DataSizeFormatter.unitFrom(size.bytes)
- val actualFromRawValue = DataSizeFormatter.unitFrom(size)
- assertEquals(expected, actualFromDataSize)
- assertEquals(expected, actualFromRawValue)
- }
-
- @Test
- fun `Megabytes is returned as data size unit when expected`() {
- val expected = DataSizeUnit.Megabytes
- val size = 500.megabytes.inBytes
- val actualFromDataSize = DataSizeFormatter.unitFrom(size.bytes)
- val actualFromRawValue = DataSizeFormatter.unitFrom(size)
- assertEquals(expected, actualFromDataSize)
- assertEquals(expected, actualFromRawValue)
- }
-
- @Test
- fun `Gigabytes is returned as data size unit when expected`() {
- val expected = DataSizeUnit.Gigabytes
- val size = 500.gigabytes.inBytes
- val actualFromDataSize = DataSizeFormatter.unitFrom(size.bytes)
- val actualFromRawValue = DataSizeFormatter.unitFrom(size)
- assertEquals(expected, actualFromDataSize)
- assertEquals(expected, actualFromRawValue)
- }
-
- @Test
- fun `Terabytes is returned as data size unit when expected`() {
- val expected = DataSizeUnit.Terabytes
- val size = 500.terabytes.inBytes
- val actualFromDataSize = DataSizeFormatter.unitFrom(size.bytes)
- val actualFromRawValue = DataSizeFormatter.unitFrom(size)
- assertEquals(expected, actualFromDataSize)
- assertEquals(expected, actualFromRawValue)
- }
-
- @Test
- fun `Format method returns correct string with 0 decimals`() {
- val expected = "5 GB"
- val size = 5.32.gigabytes.inBytes
- val actualFromDataSize =
- DataSizeFormatter.format(
- value = size.bytes,
- unit = DataSizeUnit.Gigabytes,
- decimals = 0
- )
- val actualFromRawValue = DataSizeFormatter.format(
- value = size,
- unit = DataSizeUnit.Gigabytes,
- decimals = 0
- )
- assertEquals(expected, actualFromDataSize)
- assertEquals(expected, actualFromRawValue)
- }
-
- @Test
- fun `Format method returns correct string with 1 decimals`() {
- val expected = "5,3 GB"
- val size = 5.32.gigabytes.inBytes
- val actualFromDataSize =
- DataSizeFormatter.format(
- value = size.bytes,
- unit = DataSizeUnit.Gigabytes,
- decimals = 1
- )
- val actualFromRawValue = DataSizeFormatter.format(
- value = size,
- unit = DataSizeUnit.Gigabytes,
- decimals = 1
- )
- assertEquals(expected, actualFromDataSize)
- assertEquals(expected, actualFromRawValue)
- }
-
- @Test
- fun `Format method returns correct string with 2 decimals`() {
- val expected = "5,32 GB"
- val size = 5.32.gigabytes.inBytes
- val actualFromDataSize =
- DataSizeFormatter.format(
- value = size.bytes,
- unit = DataSizeUnit.Gigabytes,
- decimals = 2
- )
- val actualFromRawValue = DataSizeFormatter.format(
- value = size,
- unit = DataSizeUnit.Gigabytes,
- decimals = 2
- )
- assertEquals(expected, actualFromDataSize)
- assertEquals(expected, actualFromRawValue)
- }
-}
\ No newline at end of file
diff --git a/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DataSizeTest.kt b/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DataSizeTest.kt
index 5520617..bbecf14 100644
--- a/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DataSizeTest.kt
+++ b/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DataSizeTest.kt
@@ -3,408 +3,348 @@
* Licensed under the Apache License, Version 2.0.
* See http://www.apache.org/licenses/LICENSE-2.0
*/
-
package io.github.ardiien.datasize
+import io.github.ardiien.datasize.DataSize.Companion.binary
+import io.github.ardiien.datasize.DataSize.Companion.decimal
+import io.github.ardiien.datasize.unit.BinaryUnit
+import io.github.ardiien.datasize.unit.DecimalUnit
+import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
-import io.github.ardiien.datasize.DataSize.Companion.bytes
-import io.github.ardiien.datasize.DataSize.Companion.gigabytes
-import io.github.ardiien.datasize.DataSize.Companion.kilobytes
-import io.github.ardiien.datasize.DataSize.Companion.megabytes
-import io.github.ardiien.datasize.DataSize.Companion.terabytes
import kotlin.test.Test
-import kotlin.test.assertEquals
class DataSizeTest {
@Test
- fun `negative number in DataSizeUnit init throws IllegalStateException`() {
+ fun `negative value throws on DataSizeUnit init`() {
+ assertThrows(IllegalStateException::class.java) {
+ (-3).decimal.kilobytes
+ }
assertThrows(IllegalStateException::class.java) {
- (-3).kilobytes
+ (-3).binary.kibibytes
}
}
@Test
- fun `negative number in toDataSize init throws IllegalStateException`() {
+ fun `negative value throws on toDataSize`() {
+ assertThrows(IllegalStateException::class.java) {
+ (-3).decimal.toDataSize(DecimalUnit.Kilobyte)
+ }
assertThrows(IllegalStateException::class.java) {
- (-3).toDataSize(DataSizeUnit.Kilobytes)
+ (-3).binary.toDataSize(BinaryUnit.Kibibyte)
}
}
@Test
- fun `zero byte object in number expression returns zero`() {
- val subject = DataSize.Zero
+ fun `zero DataSize returns zero for all units`() {
+ val subjectBinary = DataSize.binary.Zero
+ val subjectDecimal = DataSize.decimal.Zero
- val bytes = subject.inBytes
- val kilobytes = subject.inKilobytes
- val megabytes = subject.inMegabytes
- val gigabytes = subject.inGigabytes
- val terabytes = subject.inTerabytes
+ val binaryBytes = subjectBinary.inBytes
+ val decimalBytes = subjectDecimal.inBytes
+ val kilobytes = subjectBinary.inKilobytes
+ val kibibytes = subjectBinary.inKibibytes
+ val megabytes = subjectBinary.inMegabytes
+ val mebibytes = subjectBinary.inMebibytes
+ val gigabytes = subjectBinary.inGigabytes
+ val gibibytes = subjectBinary.inGibibytes
+ val terabytes = subjectBinary.inTerabytes
+ val tebibytes = subjectBinary.inTebibytes
+ val petabytes = subjectBinary.inPetabytes
+ val pebibytes = subjectBinary.inPebibytes
- assertEquals(0, bytes)
+ assertEquals(0, binaryBytes)
+ assertEquals(0, decimalBytes)
assertEquals(0.0, kilobytes, 0.01)
+ assertEquals(0.0, kibibytes, 0.01)
assertEquals(0.0, megabytes, 0.01)
+ assertEquals(0.0, mebibytes, 0.01)
assertEquals(0.0, gigabytes, 0.01)
+ assertEquals(0.0, gibibytes, 0.01)
assertEquals(0.0, terabytes, 0.01)
+ assertEquals(0.0, tebibytes, 0.01)
+ assertEquals(0.0, petabytes, 0.01)
+ assertEquals(0.0, pebibytes, 0.01)
}
@Test
- fun `bytes convert to bytes`() {
- val expectedBytes = 104857600L // 100 Mb
- val subject = DataSize.convert(
- expectedBytes.toDouble(),
- DataSizeUnit.Bytes,
- DataSizeUnit.Bytes,
- )
-
- val result = subject.bytes.inBytes
- assertEquals(expectedBytes, result)
- }
-
- @Test
- fun `bytes convert to megabytes`() {
+ fun `toDataSize bytes keeps value`() {
val expectedBytes = 104857600L // 100 Mb
- val subject = DataSize.convert(
- expectedBytes.toDouble(),
- DataSizeUnit.Bytes,
- DataSizeUnit.Megabytes,
- )
-
- val result = subject.megabytes.inBytes
- assertEquals(expectedBytes, result)
- }
-
- @Test
- fun `megabytes convert to bytes`() {
- val expectedBytes = 104857600L
- val expectedBytesInMegabytes = 100.0
- val subject = DataSize.convert(
- expectedBytesInMegabytes,
- DataSizeUnit.Megabytes,
- DataSizeUnit.Bytes,
- )
-
- val result = subject.bytes.inBytes
- assertEquals(expectedBytes, result)
- }
-
- @Test
- fun `bytes express with toDataSize as bytes`() {
- val expectedBytes = 104857600L // 100 Mb
- val subject = expectedBytes.toDataSize(DataSizeUnit.Bytes)
+ val subject = expectedBytes.binary.toDataSize(BinaryUnit.Byte)
val result = subject.inBytes
assertEquals(expectedBytes, result)
}
@Test
- fun `megabytes express with toDataSize as bytes`() {
+ fun `toDataSize mebibytes converts correctly`() {
val expectedBytes = 123207680L
val expectedMegaBytes = 117.5
- val subject = expectedMegaBytes.toDataSize(DataSizeUnit.Megabytes)
+ val subject = expectedMegaBytes.binary.toDataSize(BinaryUnit.Mebibyte)
val resultBytes = subject.inBytes
assertEquals(expectedBytes, resultBytes)
- val resultMegabytes = subject.inMegabytes
+ val resultMegabytes = subject.inMebibytes
assertEquals(expectedMegaBytes, resultMegabytes, 0.01)
}
@Test
- fun `bytes convert with toDouble to kilobytes`() {
+ fun `bytes toDouble kibibytes converts correctly`() {
val expectedBytes = 123207680L
val expectedKilobytes = 120320.0 // 117.5 Mb
- val subject = expectedBytes.bytes
- val result = subject.toDouble(DataSizeUnit.Kilobytes)
+ val subject = expectedBytes.binary.bytes
+ val result = subject.toDouble(BinaryUnit.Kibibyte)
assertEquals(expectedKilobytes, result, 0.01)
}
@Test
- fun `bytes convert with toInt to megabytes`() {
+ fun `bytes toInt mebibytes truncates correctly`() {
val expectedBytes = 123207680L
val expectedMegabytes = 117
- val subject = expectedBytes.bytes
- val result = subject.toInt(DataSizeUnit.Megabytes)
+ val subject = expectedBytes.binary.bytes
+ val result = subject.toInt(BinaryUnit.Mebibyte)
assertEquals(expectedMegabytes, result)
}
@Test
- fun `kilobytes convert with toLong to bytes`() {
+ fun `kibibytes toLong bytes converts correctly`() {
val expectedBytes = 126164664320L
val expectedKilobytes = 123207680L
- val subject = expectedKilobytes.kilobytes
- val result = subject.toLong(DataSizeUnit.Bytes)
+ val subject = expectedKilobytes.binary.kibibytes
+ val result = subject.toLong(BinaryUnit.Byte)
assertEquals(expectedBytes, result)
}
@Test
- fun `operator plus adds kilobytes`() {
+ fun `plus adds kibibytes correctly`() {
val expectedBytes = 4096L
- val subject = 2.kilobytes + 2.kilobytes
+ val subject = 2.binary.kibibytes + 2.binary.kibibytes
val result = subject.inBytes
assertEquals(expectedBytes, result)
}
@Test
- fun `operator minus subtracts megabytes`() {
+ fun `minus subtracts mebibytes correctly`() {
val expectedBytes = 2097152L
- val subject = 4.megabytes - 2.megabytes
+ val subject = 4.binary.mebibytes - 2.binary.mebibytes
val result = subject.inBytes
assertEquals(expectedBytes, result)
}
@Test
- fun `operator times whole number multiplication megabytes`() {
+ fun `times int multiplies megabytes`() {
val expectedMegabytes = 20.0
- val subject = 2.megabytes * 10
+ val subject = 2.decimal.megabytes * 10
val result = subject.inMegabytes
assertEquals(expectedMegabytes, result, 0.01)
}
@Test
- fun `operator times decimal point multiplication kilobytes`() {
+ fun `times decimal multiplies kilobytes`() {
val expectedKilobytes = 3.0
- val subject = 2.kilobytes * 1.5
+ val subject = 2.decimal.kilobytes * 1.5
val result = subject.inKilobytes
assertEquals(expectedKilobytes, result, 0.01)
}
@Test
- fun `operator div whole number divides kilobytes`() {
+ fun `div int divides kibibytes`() {
val expectedKilobytes = 5.0
- val subject = 10.kilobytes / 2
+ val subject = 10.binary.kibibytes / 2
- val result = subject.inKilobytes
+ val result = subject.inKibibytes
assertEquals(expectedKilobytes, result, 0.01)
}
@Test
- fun `operator div decimal point divides megabytes`() {
+ fun `div decimal divides megabytes`() {
val expectedMegabytes = 7.33
- val subject = 11.megabytes / 1.5
+ val subject = 11.decimal.megabytes / 1.5
val result = subject.inMegabytes
assertEquals(expectedMegabytes, result, 0.01)
}
@Test
- fun `operator div object divides bytes`() {
- val expectedBytes = 10.24
- val result = 1.kilobytes / 100.bytes
-
- assertEquals(expectedBytes, result, 0.01)
- }
-
- @Test
- fun `operator div object by zero throws IllegalArgumentException`() {
+ fun `div by zero scalar throws`() {
assertThrows(IllegalArgumentException::class.java) {
- 1.kilobytes / 0
+ 1.binary.kibibytes / 0
}
assertThrows(IllegalArgumentException::class.java) {
- 1.kilobytes / 0.0
+ 1.decimal.kilobytes / 0
}
- }
- @Test
- fun `operator div object by zero throws err if used ZERO`() {
assertThrows(IllegalArgumentException::class.java) {
- 1.kilobytes / DataSize.Zero
+ 1.binary.kibibytes / 0.0
+ }
+ assertThrows(IllegalArgumentException::class.java) {
+ 1.decimal.kilobytes / 0.0
}
}
@Test
- fun `operator div DataSizeUnit divided by DataSizeUnit`() {
- val expected = 10_485.76
- val result = 1.terabytes / 100.megabytes
+ fun `compareTo orders correctly`() {
+ val expectedLess = -1
+ val expectedEqual = 0
+ val expectedGreater = 1
+
+ val lessResult = 123.binary.kibibytes.compareTo(123.binary.gibibytes)
+ val equalResult = 123.binary.mebibytes.compareTo(123.binary.mebibytes)
+ val greaterResult = 123.decimal.megabytes.compareTo(123.decimal.kilobytes)
- assertEquals(expected, result, 0.01)
+ assertEquals(expectedLess, lessResult)
+ assertEquals(expectedEqual, equalResult)
+ assertEquals(expectedGreater, greaterResult)
}
@Test
- fun `megabytes toString with DataSizeUnit no decimals`() {
+ fun `toString megabytes no decimals`() {
val expected = "100 MB"
- val result = 100.megabytes.toString(DataSizeUnit.Megabytes)
+ val result = 100.decimal.megabytes.toString(DecimalUnit.Megabyte)
assertEquals(expected, result)
}
@Test
- fun `megabytes toString with DataSizeUnit and decimals`() {
+ fun `toString mebibytes with decimals`() {
val expected = "100,55 MB"
- val result = 100.55.megabytes.toString(DataSizeUnit.Megabytes, decimals = 2)
+ val result = 100.55.binary.mebibytes.toString(BinaryUnit.Mebibyte, fractionDigits = 2)
assertEquals(expected, result)
}
@Test
- fun `megabytes toString with kilobytes DataSizeUnit no decimals`() {
- val expected = "102.400 KB"
- val result = 100.megabytes.toString(DataSizeUnit.Kilobytes)
+ fun `toString mebibytes as kibibytes`() {
+ val expected = "102 400 KB"
+ val result = 100.binary.mebibytes.toString(BinaryUnit.Kibibyte)
assertEquals(expected, result)
}
@Test
- fun `terabytes toString with gigabytes DataSizeUnit no decimals`() {
+ fun `toString tebibytes as gibibytes`() {
val expected = "1024 GB"
- val result = 1.terabytes.toString(DataSizeUnit.Gigabytes)
+ val result = 1.binary.tebibytes.toString(BinaryUnit.Gibibyte)
assertEquals(expected, result)
}
@Test
- @OptIn(ExperimentalDataSizeApi::class)
- fun `megabytes toDecimalString with DataSizeUnit no decimals`() {
+ fun `toString mebibytes as decimal megabytes`() {
val expected = "500 MB"
- val result = 476.84.megabytes.toDecimalString(DataSizeUnit.Megabytes)
+ val result = 476.84.binary.mebibytes.toString(DecimalUnit.Megabyte)
assertEquals(expected, result)
}
@Test
- @OptIn(ExperimentalDataSizeApi::class)
- fun `megabytes toDecimalString with DataSizeUnit and decimals`() {
+ fun `toString mebibytes as decimal megabytes with decimals`() {
val expected = "512,5 MB"
- val result = 488.755.megabytes.toDecimalString(DataSizeUnit.Megabytes, decimals = 1)
+ val result = 488.755.binary.mebibytes.toString(DecimalUnit.Megabyte, fractionDigits = 1)
assertEquals(expected, result)
}
@Test
- @OptIn(ExperimentalDataSizeApi::class)
- fun `megabytes toDecimalString with kilobytes DataSizeUnit no decimals`() {
+ fun `toString mebibytes as decimal kilobytes`() {
val expected = "1000 KB"
- val result = 0.954.megabytes.toDecimalString(DataSizeUnit.Kilobytes)
+ val result = 0.954.binary.mebibytes.toString(DecimalUnit.Kilobyte)
assertEquals(expected, result)
}
@Test
- @OptIn(ExperimentalDataSizeApi::class)
- fun `terabytes toDecimalString with gigabytes DataSizeUnit no decimals`() {
+ fun `toString tebibytes as decimal gigabytes`() {
val expected = "255 GB"
- val result = 0.232.terabytes.toDecimalString(DataSizeUnit.Gigabytes)
+ val result = 0.232.binary.tebibytes.toString(DecimalUnit.Gigabyte)
assertEquals(expected, result)
}
@Test
- fun `megabytes toBinaryUnit of megabytes`() {
- val expected = 476.84
- val result = toBinaryUnit(500.0, DataSizeUnit.Megabytes)
-
- assertEquals(expected, result, 0.01)
- }
-
- @Test
- fun `gigabytes toBinaryUnit of terabytes`() {
- val expected = 0.232
- val subject = toBinaryUnit(255, DataSizeUnit.Gigabytes).gigabytes
- val result = subject.inTerabytes
-
- assertEquals(expected, result, 0.01)
- }
-
- @Test
- @OptIn(ExperimentalDataSizeApi::class)
- fun `kilobytes toDecimalUnit plus megabytes toBinaryUnit`() {
- val expected = 2_000_000L
- val subjectKilobytes =
- toBinaryUnit(
- // technically possible, but requires more precision.
- toDecimalUnit(976.5621, DataSizeUnit.Kilobytes),
- DataSizeUnit.Kilobytes,
- ).kilobytes
- val subjectMegabytes = toBinaryUnit(1, DataSizeUnit.Megabytes).megabytes
- val result = subjectKilobytes + subjectMegabytes
+ fun `ByteArray converts to DataSize correctly`() {
+ val expected = "50 MB"
+ val result = ByteArray(50 * 1024 * 1024).binary.bytes.toString(BinaryUnit.Mebibyte)
- assertEquals(expected, result.inBytes)
- }
-
- @Test
- fun `compareTo returns correct value`() {
- val expectedLess = -1
- val expectedEqual = 0
- val expectedGreater = 1
-
- val lessResult = 123.kilobytes.compareTo(123.gigabytes)
- val equalResult = 123.megabytes.compareTo(123.megabytes)
- val greaterResult = 123.megabytes.compareTo(123.kilobytes)
-
- assertEquals(expectedLess, lessResult)
- assertEquals(expectedEqual, equalResult)
- assertEquals(expectedGreater, greaterResult)
+ assertEquals(expected, result)
}
@Test
- fun `show correct decimalSeparator`() {
+ fun `decimal separator formats correctly`() {
val expected = "1,1 MB"
- val result = 1.1.megabytes.toString(unit = DataSizeUnit.Megabytes, decimals = 1)
+ val result = 1.1.binary.mebibytes.toString(unit = BinaryUnit.Mebibyte, fractionDigits = 1)
assertEquals(expected, result)
}
@Test
- fun `show correct groupingSeparator`() {
- val expected = "10.000 MB"
- val result = 10_000.megabytes.toString(unit = DataSizeUnit.Megabytes, decimals = 1)
+ fun `grouping separator formats correctly`() {
+ val expected = "10 000 MB"
+ val result = 10_000.binary.mebibytes.toString(unit = BinaryUnit.Mebibyte, fractionDigits = 1)
assertEquals(expected, result)
}
@Test
- fun `show correct groupingSeparator with floating point`() {
- val expected = "100.000,5 MB"
- val result = 100_000.5.megabytes.toString(unit = DataSizeUnit.Megabytes, decimals = 1)
+ fun `grouping with decimals formats correctly`() {
+ val expected = "100 000,5 MB"
+ val result = 100_000.5.binary.mebibytes.toString(unit = BinaryUnit.Mebibyte, fractionDigits = 1)
assertEquals(expected, result)
}
@Test
- fun `hide groupingSeparator when groupingSize less then 3`() {
+ fun `no grouping when size less than three`() {
val expected = "1000 MB"
- val result = 1000.megabytes.toString(unit = DataSizeUnit.Megabytes, decimals = 1)
+ val result = 1000.binary.mebibytes.toString(unit = BinaryUnit.Mebibyte, fractionDigits = 1)
assertEquals(expected, result)
}
@Test
- fun `orZero returns 0 bytes when value is null`() {
+ fun `orBinaryZero returns zero bytes for null`() {
+ val x: DataSize? = null
+ assertEquals(0, x.orBinaryZero().inBytes)
+ }
+
+ @Test
+ fun `orDecimalZero returns zero kilobytes for null`() {
val x: DataSize? = null
- assertEquals(0, x.orZero().inBytes)
+ assertEquals(0.0, x.orDecimalZero().inKilobytes, 0.0)
}
@Test
- fun `orZero returns 0 kilobytes when value is null`() {
+ fun `orBinaryZero returns zero mebibytes for null`() {
val x: DataSize? = null
- assertEquals(0.0, x.orZero().inKilobytes, 0.0)
+ assertEquals(0.0, x.orBinaryZero().inMebibytes, 0.0)
}
@Test
- fun `orZero returns 0 megabytes when value is null`() {
+ fun `orDecimalZero returns zero gigabytes for null`() {
val x: DataSize? = null
- assertEquals(0.0, x.orZero().inMegabytes, 0.0)
+ assertEquals(0.0, x.orDecimalZero().inGigabytes, 0.0)
}
@Test
- fun `orZero returns 0 gigabytes when value is null`() {
+ fun `orBinaryZero returns zero terabytes for null`() {
val x: DataSize? = null
- assertEquals(0.0, x.orZero().inGigabytes, 0.0)
+ assertEquals(0.0, x.orBinaryZero().inTerabytes, 0.0)
}
@Test
- fun `orZero returns 0 terabytes when value is null`() {
+ fun `orDecimalZero returns zero petabytes for null`() {
val x: DataSize? = null
- assertEquals(0.0, x.orZero().inTerabytes, 0.0)
+ assertEquals(0.0, x.orDecimalZero().inPetabytes, 0.0)
}
}
\ No newline at end of file
diff --git a/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DefaultDataSizeFormatterTest.kt b/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DefaultDataSizeFormatterTest.kt
new file mode 100644
index 0000000..d5c5c11
--- /dev/null
+++ b/datasize-core/src/test/kotlin/io/github/ardiien/datasize/DefaultDataSizeFormatterTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2026 ardiien
+ * Licensed under the Apache License, Version 2.0.
+ * See http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.github.ardiien.datasize
+
+import io.github.ardiien.datasize.DataSize.Companion.binary
+import io.github.ardiien.datasize.formatter.DefaultDataSizeFormatter
+import io.github.ardiien.datasize.unit.BinaryUnit
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+
+class DefaultDataSizeFormatterTest {
+
+ private val formatter: DefaultDataSizeFormatter = DefaultDataSizeFormatter(DefaultDataSizeFormatter.createFormat())
+
+ @Test
+ fun `bytes returns Byte unit`() {
+ val expected = BinaryUnit.Byte
+
+ val subject = 500.binary.bytes
+ val actual = formatter.unitFrom(subject)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `kibibytes returns Kibibyte unit`() {
+ val expected = BinaryUnit.Kibibyte
+
+ val subject = 500.binary.kibibytes
+ val actual = formatter.unitFrom(subject)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `mebibytes returns Mebibyte unit`() {
+ val expected = BinaryUnit.Mebibyte
+
+ val subject = 500.binary.mebibytes
+ val actual = formatter.unitFrom(subject)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `gibibytes returns Gibibyte unit`() {
+ val expected = BinaryUnit.Gibibyte
+
+ val subject = 500.binary.gibibytes
+ val actual = formatter.unitFrom(subject)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `tebibytes returns Tebibyte unit`() {
+ val expected = BinaryUnit.Tebibyte
+
+ val subject = 500.binary.tebibytes
+ val actual = formatter.unitFrom(subject)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `pebibytes returns Pebibyte unit`() {
+ val expected = BinaryUnit.Pebibyte
+
+ val subject = 500.binary.pebibytes
+ val actual = formatter.unitFrom(subject)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `format with zero fraction digits returns correct string`() {
+ val expected = "5 GB"
+
+ val size = 5.32.binary.gibibytes
+ val actual = formatter.format(
+ value = size,
+ unit = BinaryUnit.Gibibyte,
+ fractionDigits = 0,
+ )
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `format with one fraction digit returns correct string`() {
+ val expected = "5,3 GB"
+
+ val size = 5.32.binary.gibibytes
+ val actual = formatter.format(
+ value = size,
+ unit = BinaryUnit.Gibibyte,
+ fractionDigits = 1,
+ )
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `format with two fraction digits returns correct string`() {
+ val expected = "5,32 GB"
+
+ val size = 5.32.binary.gibibytes
+ val actual = formatter.format(
+ value = size,
+ unit = BinaryUnit.Gibibyte,
+ fractionDigits = 2,
+ )
+
+ assertEquals(expected, actual)
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e5930f5..90d4622 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,11 +2,13 @@
bcv = "0.18.1"
dokka = "2.1.0"
kotlin = "2.3.0"
+kotlinCollections = "0.4.0"
shadow = "8.3.9"
publish = "0.36.0"
[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
+kotlinx-collections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinCollections" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
[plugins]
diff --git a/samples/src/main/kotlin/sample-basic-01.kt b/samples/src/main/kotlin/sample-basic-01.kt
index 5b75488..f96f9c6 100644
--- a/samples/src/main/kotlin/sample-basic-01.kt
+++ b/samples/src/main/kotlin/sample-basic-01.kt
@@ -1,10 +1,11 @@
-import io.github.ardiien.datasize.DataSize.Companion.kilobytes
+import io.github.ardiien.datasize.DataSize.Companion.binary
+import io.github.ardiien.datasize.DataSize.Companion.decimal
fun main() {
- val kilobyteFromInt = 1.kilobytes
- val kilobyteFromDouble = 1.0.kilobytes
- val kilobyteFromLong = 1L.kilobytes
+ val kilobyteFromInt = 1.binary.kibibytes
+ val kilobyteFromDouble = 1.0.decimal.kilobytes
+ val kilobyteFromLong = 1L.binary.kibibytes
println(kilobyteFromInt.inBytes)
println(kilobyteFromDouble.inBytes)
diff --git a/samples/src/main/kotlin/sample-basic-02.kt b/samples/src/main/kotlin/sample-basic-02.kt
index bd33af6..06b318a 100644
--- a/samples/src/main/kotlin/sample-basic-02.kt
+++ b/samples/src/main/kotlin/sample-basic-02.kt
@@ -1,19 +1,21 @@
-import io.github.ardiien.datasize.DataSize.Companion.megabytes
-import io.github.ardiien.datasize.DataSizeUnit
+import io.github.ardiien.datasize.DataSize.Companion.binary
+import io.github.ardiien.datasize.DataSize.Companion.decimal
+import io.github.ardiien.datasize.unit.BinaryUnit
+import io.github.ardiien.datasize.unit.DecimalUnit
fun main() {
- val addition = 5.megabytes + 15.megabytes
- val substraction = 105.megabytes - 5.megabytes
+ val addition = 5.binary.mebibytes + 15.binary.mebibytes
+ val substraction = 105.decimal.megabytes - 5.decimal.megabytes
- val multiplication = 5.megabytes * 2
- val division = 15.megabytes / 2
- val remainder = 11.megabytes % 2.megabytes
+ val multiplication = 5.decimal.megabytes * 2
+ val division = 15.binary.mebibytes / 2
+ val remainder = 11.decimal.megabytes % 2.decimal.megabytes
- println(addition.toString(DataSizeUnit.Megabytes, decimals = 1))
- println(substraction.toString(DataSizeUnit.Megabytes, decimals = 1))
+ println(addition.toString(BinaryUnit.Mebibyte, fractionDigits = 1))
+ println(substraction.toString(DecimalUnit.Megabyte, fractionDigits = 1))
- println(multiplication.toString(DataSizeUnit.Megabytes, decimals = 1))
- println(division.toString(DataSizeUnit.Megabytes, decimals = 1))
- println(remainder.toString(DataSizeUnit.Megabytes, decimals = 1))
+ println(multiplication.toString(DecimalUnit.Megabyte, fractionDigits = 1))
+ println(division.toString(BinaryUnit.Mebibyte, fractionDigits = 1))
+ println(remainder.toString(DecimalUnit.Megabyte, fractionDigits = 1))
}
\ No newline at end of file
diff --git a/samples/src/main/kotlin/sample-basic-03.kt b/samples/src/main/kotlin/sample-basic-03.kt
index e927bf7..e72ab45 100644
--- a/samples/src/main/kotlin/sample-basic-03.kt
+++ b/samples/src/main/kotlin/sample-basic-03.kt
@@ -1,21 +1,20 @@
-import io.github.ardiien.datasize.DataSize.Companion.kilobytes
-import io.github.ardiien.datasize.DataSize.Companion.megabytes
+import io.github.ardiien.datasize.DataSize.Companion.binary
import io.github.ardiien.datasize.max
import io.github.ardiien.datasize.min
fun main() {
val sortedList = listOf(
- 1.kilobytes, 1.megabytes, 20.kilobytes
+ 1.binary.kibibytes, 1.binary.mebibytes, 20.binary.kibibytes
).sorted()
- val gt = 15.kilobytes > 1.kilobytes
- val lte = 15.kilobytes <= 14.kilobytes
- val eq = 15.kilobytes == 15.kilobytes
- val neq = 15.kilobytes != 5.kilobytes
+ val gt = 15.binary.kibibytes > 1.binary.kibibytes
+ val lte = 15.binary.kibibytes <= 14.binary.kibibytes
+ val eq = 15.binary.kibibytes == 15.binary.kibibytes
+ val neq = 15.binary.kibibytes != 5.binary.kibibytes
- val min = min(2.megabytes, 2.kilobytes)
- val max = max(2.megabytes, 2.kilobytes)
+ val min = min(2.binary.mebibytes, 2.binary.kibibytes)
+ val max = max(2.binary.mebibytes, 2.binary.kibibytes)
println(sortedList)
diff --git a/samples/src/main/kotlin/sample-basic-04.kt b/samples/src/main/kotlin/sample-basic-04.kt
index f8150b1..bae7ee0 100644
--- a/samples/src/main/kotlin/sample-basic-04.kt
+++ b/samples/src/main/kotlin/sample-basic-04.kt
@@ -1,13 +1,14 @@
-import io.github.ardiien.datasize.DataSize.Companion.kilobytes
-import io.github.ardiien.datasize.DataSizeFormatter
+import io.github.ardiien.datasize.DataSize.Companion.binary
+import io.github.ardiien.datasize.formatter.DefaultDataSizeFormatter
fun main() {
- val value = 55.563.kilobytes
+ val formatter = DefaultDataSizeFormatter(DefaultDataSizeFormatter.createFormat())
+ val value = 55.563.binary.kibibytes
- val defaultPrecision = DataSizeFormatter.format(value, decimals = 0)
- val betterPrecision = DataSizeFormatter.format(value, decimals = 1)
- val maxAvailablePrecision = DataSizeFormatter.format(value, decimals = 2)
+ val defaultPrecision = formatter.format(value, fractionDigits = 0)
+ val betterPrecision = formatter.format(value, fractionDigits = 1)
+ val maxAvailablePrecision = formatter.format(value, fractionDigits = 2)
println(defaultPrecision)
println(betterPrecision)