Full Code of kevincianfarini/alchemist for AI

trunk 9f0101a7c146 cached
71 files
290.8 KB
76.7k tokens
1 requests
Download .txt
Showing preview only (317K chars total). Download the full file or copy to clipboard to get everything.
Repository: kevincianfarini/alchemist
Branch: trunk
Commit: 9f0101a7c146
Files: 71
Total size: 290.8 KB

Directory structure:
gitextract_1pez5g1s/

├── .github/
│   ├── .java-version
│   ├── renovate.json5
│   └── workflows/
│       ├── gradle-wrapper.yaml
│       ├── release.yaml
│       └── test.yaml
├── .gitignore
├── LICENSE.txt
├── ModuleDocumentation.md
├── README.md
├── build.gradle.kts
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src/
    ├── commonMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       ├── internal/
    │                       │   ├── SaturatingLong.kt
    │                       │   ├── double.kt
    │                       │   ├── duration.kt
    │                       │   └── exception.kt
    │                       ├── scalar/
    │                       │   ├── acceleration.kt
    │                       │   ├── area.kt
    │                       │   ├── energy.kt
    │                       │   ├── force.kt
    │                       │   ├── length.kt
    │                       │   ├── mass.kt
    │                       │   ├── power.kt
    │                       │   ├── temperature.kt
    │                       │   ├── velocity.kt
    │                       │   └── volume.kt
    │                       ├── type/
    │                       │   ├── Acceleration.kt
    │                       │   ├── Area.kt
    │                       │   ├── Energy.kt
    │                       │   ├── Force.kt
    │                       │   ├── Length.kt
    │                       │   ├── Mass.kt
    │                       │   ├── Power.kt
    │                       │   ├── Temperature.kt
    │                       │   ├── Velocity.kt
    │                       │   ├── Volume.kt
    │                       │   └── duration.kt
    │                       └── unit/
    │                           ├── AreaUnit.kt
    │                           ├── EnergyUnit.kt
    │                           ├── ForceUnit.kt
    │                           ├── LengthUnit.kt
    │                           ├── MassUnit.kt
    │                           ├── PowerUnit.kt
    │                           ├── TemperatureUnit.kt
    │                           └── VolumeUnit.kt
    ├── commonTest/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       ├── Long.kt
    │                       ├── ignore.kt
    │                       ├── internal/
    │                       │   ├── DoubleTest.kt
    │                       │   └── SaturatingLongTest.kt
    │                       └── type/
    │                           ├── AccelerationTest.kt
    │                           ├── AreaTest.kt
    │                           ├── EnergyTest.kt
    │                           ├── ForceTest.kt
    │                           ├── LengthTest.kt
    │                           ├── PowerTest.kt
    │                           ├── TemperatureTest.kt
    │                           ├── VelocityTest.kt
    │                           └── VolumeTest.kt
    ├── jsMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.js.kt
    ├── jsTest/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── ignore.js.kt
    ├── jvmMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.jvm.kt
    ├── nativeMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.native.kt
    ├── wasmJsMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.wasmjs.kt
    ├── wasmJsTest/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── ignore.wasmjs.kt
    ├── wasmWasiMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.wasmwasi.kt
    └── wasmWasiTest/
        └── kotlin/
            └── io/
                └── github/
                    └── kevincianfarini/
                        └── alchemist/
                            └── ignore.wasmwasi.kt

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/.java-version
================================================
24


================================================
FILE: .github/renovate.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',
  extends: [
    'config:recommended',
  ],
  reviewers: ['kevincianfarini'],
  ignorePresets: [
    // Ensure we get the latest version and are not pinned to old versions.
    'workarounds:javaLTSVersions',
  ],
  customManagers: [
    // Update .java-version file with the latest JDK version.
    {
      customType: 'regex',
      fileMatch: [
        '\\.java-version$',
      ],
      matchStrings: [
        '(?<currentValue>.*)\\n',
      ],
      datasourceTemplate: 'java-version',
      depNameTemplate: 'java',
      // Only write the major version.
      extractVersionTemplate: '^(?<version>\\d+)',
    },
  ],
}

================================================
FILE: .github/workflows/gradle-wrapper.yaml
================================================
name: gradle-wrapper

on:
  pull_request:
    paths:
      - 'gradlew'
      - 'gradlew.bat'
      - 'gradle/wrapper/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: gradle/wrapper-validation-action@v3

================================================
FILE: .github/workflows/release.yaml
================================================
name: release

on:
  push:
    branches:
      - 'trunk'

env:
  GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"

jobs:
  publish:
    runs-on: macos-latest

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version-file: .github/.java-version

      - name: Build and publish artifacts
        env:
          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }}
          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }}
          ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGINMEMORYKEY }}
        run: ./gradlew dokkaGenerate publish

      - name: Deploy docs to website
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BRANCH: site
          FOLDER: build/dokka/html
          TARGET_FOLDER: docs/0.x/
          CLEAN: true

================================================
FILE: .github/workflows/test.yaml
================================================
name: test

on:
  pull_request: {}
  push:
    branches:
      - 'trunk'

env:
  GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"

jobs:
  test:
    strategy:
      matrix:
        os: [macOS-latest, windows-latest, ubuntu-latest]
        include:
          - os: macOS-latest
            job: iosSimulatorArm64Test iosX64Test macosArm64Test macosX64Test tvosSimulatorArm64Test tvosX64Test watchosSimulatorArm64Test watchosX64Test
          - os: windows-latest
            job: mingwX64Test
          - os: ubuntu-latest
            job: jsTest jvmTest linuxX64Test wasmJsTest wasmWasiTest

    runs-on: ${{matrix.os}}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version-file: .github/.java-version

      - run: ./gradlew ${{matrix.job}}

  kmp-missing-targets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version-file: .github/.java-version
      - run: ./gradlew kmpMissingTargets


================================================
FILE: .gitignore
================================================
.idea
.gradle
**/build
local.properties
.kotlin

================================================
FILE: LICENSE.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: ModuleDocumentation.md
================================================
# Module alchemist

Alchemist allows you to manage physical quanities defined in the [International System of Units](https://en.wikipedia.org/wiki/International_System_of_Units). 
Like [Duration](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.time/-duration/), alchemist models its quanities as a value class with a single underlying `Long` value. 

### Type Safety

Alchemist's main goal is to provide type safety for arithmetic on physical quanities. There's no need to pass loosely 
typed `Long`, `Int`, or `Double` values around anymore! 

```kt
val energy1 = 10.wattHours
val energy2 = 10.millijoules
println(energy1 + energy2) // OK: both are type Energy.

val power = 10.watts
println(energy1 + power) // Compiler Error! 
```

### Scalar Arithmetic 

Physical quantities expose scalar arithmetic like the following:

```kt
val power = 10.watts
println(power / 2) // "5W"
println(power * 100) // "1kW"
println(-power) // "-10W"
```

and each quantity exposes typed arithmetic: 

```kt
val first = 10.wattHours
val second = 2.wattHours
println(first + second) // "12Wh"
println(first - second) // "8Wh"
println(second - first) // "-8Wh"
println(first / second) // "5.0"
```

### Physical Arithmetic 

Physical quantities expose valid physical arithmetic defined in the International System of Units. For example: 

```kt
val energy = 10.wattHours
val power = 10.watts
val duration: Duration = energy / power
println(duration) // "1h"

val length = 10.nanometers
val force: Force = energy / length
println(force) // "3.6TN"

val velocity = length / 1.seconds
println(velocity) // 10 nm/s

val acceleration = velocity / 1.seconds
println(acceleration) // 10 nm/s²
```

### Integration with the Kotlin Standard Library 

Kotlin provides a measure of time, called [`Duration`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.time/-duration/), 
in its standard library. Alchemist integrates with Duration for physical arithmetic rather than supplying its own 
implementation of time. 

```kt
val time: kotlin.time.Duration = 1.hours
val energy = time * 1.watts
println(energy) // "1Wh"
println(energy / time) // "1W"

val velocity = 1.meters / time
println(velocity) // "1 m/s"
println(velocity * time) // "1m"

val acceleration = velocity / time
println(velocity) // "1 m/s²
println(acceleration * time) // "1 m/s"
```

### Custom Quantity Unit Implementation 

Every unit alchemist exposes is an interface, thus allowing you to implement non-standard or uncommonly used units: 

```kt
// Alchemist does not provide horsepower out of the box. 
object HorsePower : PowerUnit {
    override val symbol: String get() = "hp"
    override val microwattScale: Long get() = 745_699_871
}

val Int.horsepower: Power get() = this.toPower(HorsePower)

println(1.horsepower) // "745.7W"
```

Note that alchemist does not provide certain units out of the box because they're impossible to represent without losing 
precision. In the above example, 1 horsepower is actually 745,699,871.58μW.

### Infinity 

While Long values can store huge numbers, sometimes they're not enough for the operations you're attempting to 
perform. Rather than silently over or underflowing during an arithmetic operation, Alchemist will clamp your quantities 
to a special infinite value. These special infinite values have some restrictions. 

```kt
// This would overflow. 
val infiniteEnergy = 100.joules * Long.MAX_VALUE 
println(infiniteEnergy) // "Infinity"

// Infinite values don't behave like other values. 
println(infiniteEnergy / Long.MAX_VALUE) // "Infinity"
println(infiniteEnergy * 10) // "Infinity"
println(-infiniteEnergy) // "-Infinity"

// Some operations are invalid with infinite values. 
println(infiniteEnergy / infiniteEnergy) // Oops! This throws IllegalArgumentException. 
println(infiniteEnergy * -infiniteEnergy) // This throws, too. 
```

### We're alchemists, not physicists! 

Alchemist is not built with physics engines in mind. Instead, Alchemist aims to provide type safety and reasonable levels of precision 
that are sufficient for most business use cases. For example, Alchemist aims to help you model how much energy an entire country used 
for a year with a reasonable level of precision, but it won't help you model how much energy the Sun produces in a day. 

Additionally, Alchemist values type safety over modeling arbitrary quantities derived from SI base units. 
Introducing that functionality involves too many compromises and would be better handled by a different library with different goals.

If you need to model things like how much energy your air conditioning used in a year, or how far your compact electric vehicle charged 
with the electricity from your solar panels can drive, great! Alchemist might be a good fit for you. We hope you like it. 


================================================
FILE: README.md
================================================
# Alchemist

Manage physical units. Inspired by kotlin.time.Duration. Alchemist allow type safe arithmetic between different 
physical quantities defined in the [International System of Units](https://en.wikipedia.org/wiki/International_System_of_Units#).

```kt
val time: Duration = 10.seconds
val length: Length = 10.kilometers
val velocity: Velocity = length / time
val acceleration: Acceleration = velocity / time
val mass: Mass = 10.kilograms
val force: Force = acceleration * mass
val energy: Energy = force * length 
val power: Power = energy / time
val area: Area = length * length 
val volume: Volume = length * length * length
```

## Download

```toml
[versions]
alchemist = "0.2.0"

[libraries]
alchemist = { module = "io.github.kevincianfarini.alchemist:alchemist", version.ref = "alchemist" }
```

## Alchemist's Goals 

1. Model physical quantities as Kotlin value classes which wrap a single `Long` value. 
2. Provide logical arithmetic between different physical quantities, like `power = energy / time`. 
3. Allow for the implementation of custom units on physical quanities that Alchemist does not provide, such as horsepower as a unit of `Power` or Rankine degrees for `Temperature`. 
4. (In the future) Allow different order of magnitude precision and wider ranges of valid values. 
5. Easy extensibility for custom formulas, such as `energyₖ = ½ * mass * velocity²`.

## Alchemist's Non-Goals

1. Using generic or floating point values as the underlying storage mechanism. 
2. Representing arbitrary formulaic expressions. 
3. Providing as many formulaic conversions as possible out of the box, such as `energyₖ = ½ * mass * velocity²`.
4. Infinitely precise values. 
5. Infinitely large ranges of valid values. 

## Platform Support 

| Platform             | Compiled | Tested in CI                                                                                       |
|----------------------|----------|----------------------------------------------------------------------------------------------------|
| androidNativeArm32   | ✅        | ❌                                                                                                  |
| androidNativeArm64   | ✅        | ❌                                                                                                  |
| androidNativeX64     | ✅        | ❌                                                                                                  |
| androidNativeX86     | ✅        | ❌                                                                                                  |
| iosArm64             | ✅        | ❌                                                                                                  |
| iosSimulatorArm64    | ✅        | ✅                                                                                                  |
| iosX64               | ✅        | ✅                                                                                                  |
| js                   | ✅        | ✅                                                                                                  |
| jvm                  | ✅        | ✅                                                                                                  |
| linuxArm64           | ✅        | ❌ (Prohibited by [Tier 2](https://kotlinlang.org/docs/native-target-support.html#tier-2) support.) |
| linuxX64             | ✅        | ✅                                                                                                  |
| macosArm64           | ✅        | ✅                                                                                                  |
| macosX64             | ✅        | ✅                                                                                                  |
| mingwX64             | ✅        | ✅                                                                                                  |
| tvosArm64            | ✅        | ❌                                                                                                  |
| tvosSimulatorArm64   | ✅        | ✅                                                                                                  |
| tvosX64              | ✅        | ✅                                                                                                  |
| wasmJs               | ✅        | ✅                                                                                                  |
| wasmWasi             | ✅        | ⚠️ (Some tests don't run because of [KT-60964](https://youtrack.jetbrains.com/issue/KT-60964).)    |
| watchosArm32         | ✅        | ❌                                                                                                  |
| watchosArm64         | ✅        | ❌                                                                                                  |
| watchosDeviceArm64   | ✅        | ❌                                                                                                  |
| watchosSimuatorArm64 | ✅        | ✅                                                                                                  |
| watchosX64           | ✅        | ✅                                                                                                  |


================================================
FILE: build.gradle.kts
================================================
import com.vanniktech.maven.publish.SonatypeHost
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
    alias(libs.plugins.dokka)
    alias(libs.plugins.kmpmt)
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.publish)
}

@OptIn(ExperimentalWasmDsl::class)
kotlin {

    explicitApi()

    androidNativeArm32()
    androidNativeArm64()
    androidNativeX64()
    androidNativeX86()
    iosArm64()
    iosSimulatorArm64()
    iosX64()
    js {
        nodejs {
            testTask {
                useMocha {
                    timeout = "5s"
                }
            }
        }
    }
    jvm {
        compilations.configureEach {
            compilerOptions.options.apply {
                jvmTarget = JvmTarget.JVM_1_8
                freeCompilerArgs.add("-Xjvm-default=all")
            }
        }
    }
    linuxArm64()
    linuxX64()
    macosArm64()
    macosX64()
    mingwX64()
    tvosArm64()
    tvosSimulatorArm64()
    tvosX64()
    wasmJs {
        nodejs {
            testTask {
                useMocha {
                    timeout = "5s"
                }
            }
        }
    }
    wasmWasi { nodejs() }
    watchosArm32()
    watchosArm64()
    watchosDeviceArm64()
    watchosSimulatorArm64()
    watchosX64()

    sourceSets {

        configureEach {
            if (name.endsWith("Test")) {
                compilerOptions {
                    freeCompilerArgs.add("-Xexpect-actual-classes")
                }
            }
        }

        commonTest.dependencies {
            implementation(libs.kotlin.test.core)
        }
    }
}

tasks.withType<AbstractTestTask> {
    testLogging {
        exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
        showStandardStreams = true
        showStackTraces = true
    }
}

mavenPublishing {
    publishToMavenCentral(host = SonatypeHost.S01, automaticRelease = true)
    signAllPublications()
}

dokka {
    val versionName = project.findProperty("VERSION_NAME") as String
    val version = if (versionName.contains("-SNAPSHOT")) "trunk" else versionName
    dokkaSourceSets.commonMain {
        includes.from("ModuleDocumentation.md")
        sourceLink {
            localDirectory.set(projectDir.resolve("src"))
            remoteUrl("https://github.com/kevincianfarini/alchemist/tree/${version}/src")
            remoteLineSuffix.set("#L")
        }
    }
}


================================================
FILE: gradle/libs.versions.toml
================================================
[versions]
dokka = "2.0.0"
kmpmt = "0.1.1"
kotlin = "2.1.21"
publish = "0.30.0"

[libraries]
kotlin-test-core = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }

[plugins]
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
kmpmt = { id = "com.jakewharton.kmp-missing-targets", version.ref = "kmpmt" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
publish = { id = "com.vanniktech.maven.publish", version.ref = "publish" }


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


================================================
FILE: gradle.properties
================================================
kotlin.code.style=official

GROUP=io.github.kevincianfarini.alchemist
POM_ARTIFACT_ID=alchemist
VERSION_NAME=0.2.1-SNAPSHOT

POM_NAME=Alchemist
POM_DESCRIPTION=Type safe management and arithmetic of physical units. Inspired by kotlin.time.Duration.
POM_INCEPTION_YEAR=2024

POM_URL=https://github.com/kevincianfarini/alchemist
POM_SCM_URL=https://github.com/kevincianfarini/alchemist
POM_SCM_CONNECTION=scm:git:git://github.com/kevincianfarini/alchemist.git
POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/kevincianfarini/alchemist.git

POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo

POM_DEVELOPER_ID=kevincianfarini
POM_DEVELOPER_NAME=Kevin Cianfarini
POM_DEVELOPER_URL=https://github.com/kevincianfarini

SONATYPE_CONNECT_TIMEOUT_SECONDS=120
SONATYPE_CLOSE_TIMEOUT_SECONDS=900

org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers


================================================
FILE: gradlew
================================================
#!/bin/sh

#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#

##############################################################################
#
#   Gradle start up script for POSIX generated by Gradle.
#
#   Important for running:
#
#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
#       noncompliant, but you have some other compliant shell such as ksh or
#       bash, then to run this script, type that shell name before the whole
#       command line, like:
#
#           ksh Gradle
#
#       Busybox and similar reduced shells will NOT work, because this script
#       requires all of these POSIX shell features:
#         * functions;
#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
#         * compound commands having a testable exit status, especially «case»;
#         * various built-in commands including «command», «set», and «ulimit».
#
#   Important for patching:
#
#   (2) This script targets any POSIX shell, so it avoids extensions provided
#       by Bash, Ksh, etc; in particular arrays are avoided.
#
#       The "traditional" practice of packing multiple parameters into a
#       space-separated string is a well documented source of bugs and security
#       problems, so this is (mostly) avoided, by progressively accumulating
#       options in "$@", and eventually passing that to Java.
#
#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
#       see the in-line comments for details.
#
#       There are tweaks for specific operating systems such as AIX, CygWin,
#       Darwin, MinGW, and NonStop.
#
#   (3) This script is generated from the Groovy template
#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
#       within the Gradle project.
#
#       You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: $0 may be a link
app_path=$0

# Need this for daisy-chained symlinks.
while
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
    [ -h "$app_path" ]
do
    ls=$( ls -ld "$app_path" )
    link=${ls#*' -> '}
    case $link in             #(
      /*)   app_path=$link ;; #(
      *)    app_path=$APP_HOME$link ;;
    esac
done

# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
    echo "$*"
} >&2

die () {
    echo
    echo "$*"
    echo
    exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in                #(
  CYGWIN* )         cygwin=true  ;; #(
  Darwin* )         darwin=true  ;; #(
  MSYS* | MINGW* )  msys=true    ;; #(
  NONSTOP* )        nonstop=true ;;
esac

CLASSPATH="\\\"\\\""


# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD=$JAVA_HOME/jre/sh/java
    else
        JAVACMD=$JAVA_HOME/bin/java
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD=java
    if ! command -v java >/dev/null 2>&1
    then
        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
fi

# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
    case $MAX_FD in #(
      max*)
        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
        # shellcheck disable=SC2039,SC3045
        MAX_FD=$( ulimit -H -n ) ||
            warn "Could not query maximum file descriptor limit"
    esac
    case $MAX_FD in  #(
      '' | soft) :;; #(
      *)
        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
        # shellcheck disable=SC2039,SC3045
        ulimit -n "$MAX_FD" ||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
    esac
fi

# Collect all arguments for the java command, stacking in reverse order:
#   * args from the command line
#   * the main class name
#   * -classpath
#   * -D...appname settings
#   * --module-path (only if needed)
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )

    JAVACMD=$( cygpath --unix "$JAVACMD" )

    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    for arg do
        if
            case $arg in                                #(
              -*)   false ;;                            # don't mess with options #(
              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
                    [ -e "$t" ] ;;                      #(
              *)    false ;;
            esac
        then
            arg=$( cygpath --path --ignore --mixed "$arg" )
        fi
        # Roll the args list around exactly as many times as the number of
        # args, so each arg winds up back in the position where it started, but
        # possibly modified.
        #
        # NB: a `for` loop captures its iteration list before it begins, so
        # changing the positional parameters here affects neither the number of
        # iterations, nor the values presented in `arg`.
        shift                   # remove old arg
        set -- "$@" "$arg"      # push replacement arg
    done
fi


# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Collect all arguments for the java command:
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
#     and any embedded shellness will be escaped.
#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
#     treated as '${Hostname}' itself on the command line.

set -- \
        "-Dorg.gradle.appname=$APP_BASE_NAME" \
        -classpath "$CLASSPATH" \
        -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
        "$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
    die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
#   set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- $(
        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
        xargs -n1 |
        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
        tr '\n' ' '
    )" '"$@"'

exec "$JAVACMD" "$@"


================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem

@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute

echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2

goto fail

:execute
@rem Setup the command line

set CLASSPATH=


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*

:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: settings.gradle.kts
================================================
rootProject.name = "alchemist"

pluginManagement {
    repositories {
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositories {
        mavenCentral()
    }
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/internal/SaturatingLong.kt
================================================
@file:Suppress("NOTHING_TO_INLINE")

package io.github.kevincianfarini.alchemist.internal

import kotlin.jvm.JvmInline
import kotlin.math.absoluteValue
import kotlin.math.roundToLong
import kotlin.math.sign

/**
 * A [SaturatingLong] is a 64-bit signed integer that protects against overflow during multiplication, addition, and
 * subtraction. When one of those operations overflows, either [POSITIVE_INFINITY] or [NEGATIVE_INFINITY] is returned.
 * Performing operations with an infinite value will either return another infinite value if it's a valid operation,
 * or will error if it's an invalid operation.
 *
 * See [Saturation Arithmetic Intrinsics](https://llvm.org/docs/LangRef.html#saturation-arithmetic-intrinsics) from
 * LLVM for more details.
 */
@JvmInline
internal value class SaturatingLong(val rawValue: Long) {

    operator fun plus(other: SaturatingLong): SaturatingLong = when {
        isInfinite() -> when {
            this == other || other.isFinite() -> this
            else -> {
                throwIllegalArgumentException("Summing infinite values of different signs yields an undefined result.")
            }
        }
        else -> {
            val a = rawValue
            val b = other.rawValue
            val result = rawValue + other.rawValue
            val didOverflow = (((a and b and result.inv()) or (a.inv() and b.inv() and result)) < 0L)
            when {
                didOverflow -> if (result > 0) NEGATIVE_INFINITY else POSITIVE_INFINITY
                else -> SaturatingLong(result)
            }
        }
    }

    inline operator fun plus(other: Long): SaturatingLong {
        return this + SaturatingLong(other)
    }

    inline operator fun minus(other: SaturatingLong): SaturatingLong = this + (-other)

    inline operator fun minus(other: Long): SaturatingLong {
        return this - SaturatingLong(other)
    }

    operator fun unaryMinus(): SaturatingLong = when(this) {
        POSITIVE_INFINITY -> NEGATIVE_INFINITY
        NEGATIVE_INFINITY -> POSITIVE_INFINITY
        else -> SaturatingLong(-rawValue)
    }

    operator fun times(other: SaturatingLong): SaturatingLong {
        return when {
            isInfinite() or other.isInfinite() -> when {
                rawValue == 0L || other.rawValue == 0L -> {
                    throwIllegalArgumentException("Multiplying an infinite value by zero yields an undefined result.")
                }
                rawValue.sign == other.rawValue.sign -> POSITIVE_INFINITY
                else -> NEGATIVE_INFINITY
            }
            else -> {
                val result = rawValue * other.rawValue
                val doesOverflow = rawValue != 0L && result / rawValue != other.rawValue
                when {
                    doesOverflow && rawValue.sign == other.rawValue.sign -> POSITIVE_INFINITY
                    doesOverflow -> NEGATIVE_INFINITY
                    else -> SaturatingLong(result)
                }
            }
        }
    }

    val sign: Int inline get() = rawValue.sign

    inline operator fun times(other: Long): SaturatingLong {
        return this * SaturatingLong(other)
    }

    inline operator fun times(other: Int): SaturatingLong {
        return times(other.toLong())
    }

    inline operator fun times(other: Double): SaturatingLong {
        val longScale = other.roundToLong()
        if (longScale.toDouble() == other) {
            return times(longScale)
        } else {
            val thisDouble = toDouble()
            val result = thisDouble * other
            return SaturatingLong(result.roundToLong())
        }
    }

    operator fun div(other: SaturatingLong): SaturatingLong {
        val thisInfinite = isInfinite()
        val otherInfinite = other.isInfinite()
        return when {
            thisInfinite && otherInfinite -> {
                throwIllegalArgumentException("Dividing two infinite values yields an undefined result.")
            }
            thisInfinite -> this * other.sign
            otherInfinite -> SaturatingLong(0)
            else -> SaturatingLong(rawValue / other.rawValue)
        }
    }

    operator fun div(other: Long): SaturatingLong {
        return this / SaturatingLong(other)
    }

    operator fun div(other: Int): SaturatingLong {
        return div(other.toLong())
    }

    operator fun div(other: Double): SaturatingLong {
        val longScale = other.roundToLong()
        if (longScale.toDouble() == other) {
            return div(longScale)
        } else {
            val thisDouble = toDouble()
            val result = thisDouble / other
            return SaturatingLong(result.roundToLong())
        }
    }

    operator fun rem(other: SaturatingLong): SaturatingLong {
        val thisInfinite = isInfinite()
        val otherInfinite = other.isInfinite()
        return when {
            thisInfinite && otherInfinite -> {
                throwIllegalArgumentException("Dividing two infinite values yields an undefined result.")
            }
            thisInfinite -> this * other.sign
            otherInfinite -> SaturatingLong(0)
            else -> SaturatingLong(rawValue % other.rawValue)
        }
    }

    operator fun rem(other: Long): SaturatingLong {
        return this % SaturatingLong(other)
    }

    operator fun compareTo(other: SaturatingLong): Int {
        return rawValue.compareTo(other.rawValue)
    }

    operator fun compareTo(other: Long): Int {
        return compareTo(SaturatingLong(other))
    }

    operator fun compareTo(other: Int): Int {
        return compareTo(SaturatingLong(other.toLong()))
    }

    override fun toString(): String = when (this) {
        POSITIVE_INFINITY -> "Infinity"
        NEGATIVE_INFINITY -> "-Infinity"
        else -> rawValue.toString()
    }

    inline fun isInfinite(): Boolean {
        return (this == POSITIVE_INFINITY) or (this == NEGATIVE_INFINITY)
    }

    inline fun isFinite(): Boolean = !isInfinite()

    val absoluteValue: SaturatingLong
        get() = when {
            isInfinite() -> POSITIVE_INFINITY
            else -> SaturatingLong(rawValue.absoluteValue)
        }

    fun toDouble(): Double = when (this) {
        POSITIVE_INFINITY -> Double.POSITIVE_INFINITY
        NEGATIVE_INFINITY -> Double.NEGATIVE_INFINITY
        else -> rawValue.toDouble()
    }
}

internal inline val Long.saturated get() = SaturatingLong(this)

// Inline the constants versus using `MIN_VALUE` and `MAX_VALUE` to avoid accessing Long's companion.
internal inline val POSITIVE_INFINITY get() = SaturatingLong(9223372036854775807L)
internal inline val NEGATIVE_INFINITY get() = SaturatingLong(-9223372036854775807L - 1L)


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/internal/double.kt
================================================
package io.github.kevincianfarini.alchemist.internal

/**
 * Returns a decimal string that has been rounded to the specified number of [decimals].
 */
internal expect fun Double.toDecimalString(decimals: Int): String

================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/internal/duration.kt
================================================
package io.github.kevincianfarini.alchemist.internal

import io.github.kevincianfarini.alchemist.type.Energy
import kotlin.time.Duration
import kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit

internal fun Duration.isPreciseToNanosecond(): Boolean {
    return this == inWholeNanoseconds.nanoseconds
}

internal val Duration.sign: Int get() = when {
    isPositive() -> 1
    isNegative() -> -1
    else -> 0
}

internal val DurationUnit.shortName: String get() = when (this) {
    DurationUnit.NANOSECONDS -> "ns"
    DurationUnit.MICROSECONDS -> "us"
    DurationUnit.MILLISECONDS -> "ms"
    DurationUnit.SECONDS -> "s"
    DurationUnit.MINUTES -> "m"
    DurationUnit.HOURS -> "h"
    DurationUnit.DAYS -> "d"
    else -> error("Unknown unit: $this")
}

internal val DurationUnit.shortNameSquared: String get() = when (this) {
    DurationUnit.NANOSECONDS -> "ns²"
    DurationUnit.MICROSECONDS -> "us²"
    DurationUnit.MILLISECONDS -> "ms²"
    DurationUnit.SECONDS -> "s²"
    DurationUnit.MINUTES -> "m²"
    DurationUnit.HOURS -> "h²"
    DurationUnit.DAYS -> "d²"
    else -> error("Unknown unit: $this")
}

internal val DurationUnit.secondScale: Double get() = when (this) {
    DurationUnit.NANOSECONDS -> 0.000_000_001
    DurationUnit.MICROSECONDS -> 0.000_001
    DurationUnit.MILLISECONDS -> 0.001
    DurationUnit.SECONDS -> 1.0
    DurationUnit.MINUTES -> 60.0
    DurationUnit.HOURS -> 3_600.0
    DurationUnit.DAYS -> 86_400.0
    else -> error("Unknown unit: $this")
}

internal fun Duration.toDecimalComponents(
    action: (
        kiloseconds: Long,
        seconds: Long,
        millis: Long,
        micros: Long,
        nanos: Long
    ) -> Energy,
): Energy {
    val seconds = inWholeSeconds
    val secondsRemainder = this - seconds.seconds
    val millis = secondsRemainder.inWholeMilliseconds
    val millisRemainder = secondsRemainder - millis.milliseconds
    val micros = millisRemainder.inWholeMicroseconds
    val nanos = (millisRemainder - micros.microseconds).inWholeNanoseconds
    return action(seconds / 1_000, seconds % 1_000, millis, micros, nanos)
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/internal/exception.kt
================================================
package io.github.kevincianfarini.alchemist.internal

/**
 * Ensures that we don't inline the exception throwing instructions and instead make the jump
 * to this function. This helps avoid busting the instruction cache and localizes exception throwing
 * instructions to one place.
 *
 * TODO: Supply a ProGuard rule to keep this method so it's not inlined
 */
internal fun throwIllegalArgumentException(message: String): Nothing {
    throw IllegalArgumentException(message)
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/acceleration.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Acceleration

internal val Int.nmPerSecond2: Acceleration get() = toLong().nmPerSecond2
internal val Long.nmPerSecond2: Acceleration get() = Acceleration(saturated)
internal val SaturatingLong.nmPerSecond2: Acceleration get() = Acceleration(this)


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/area.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Area
import io.github.kevincianfarini.alchemist.unit.AreaUnit
import kotlin.math.roundToLong

/**
 * Returns an [Area] equal to [Int] number of decimilliares.
 */
public inline val Int.decimilliares: Area get() = toArea(AreaUnit.Metric.Decimilliare)

/**
 * Returns an [Area] equal to [Long] number of decimilliares.
 */
public inline val Long.decimilliares: Area get() = toArea(AreaUnit.Metric.Decimilliare)

/**
 * Returns an [Area] equal to [Double] number of decimilliares. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.decimilliares: Area get() = toArea(AreaUnit.Metric.Decimilliare)

/**
 * Returns an [Area] equal to [Int] number of centiares.
 */
public inline val Int.centiares: Area get() = toArea(AreaUnit.Metric.Centiare)

/**
 * Returns an [Area] equal to [Long] number of centiares.
 */
public inline val Long.centiares: Area get() = toArea(AreaUnit.Metric.Centiare)

/**
 * Returns an [Area] equal to [Double] number of centiares. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.centiares: Area get() = toArea(AreaUnit.Metric.Centiare)

/**
 * Returns an [Area] equal to [Int] number of deciares.
 */
public inline val Int.deciares: Area get() = toArea(AreaUnit.Metric.Deciare)

/**
 * Returns an [Area] equal to [Long] number of deciares.
 */
public inline val Long.deciares: Area get() = toArea(AreaUnit.Metric.Deciare)

/**
 * Returns an [Area] equal to [Double] number of deciares. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.deciares: Area get() = toArea(AreaUnit.Metric.Deciare)

/**
 * Returns an [Area] equal to [Int] number of ares.
 */
public inline val Int.ares: Area get() = toArea(AreaUnit.Metric.Are)

/**
 * Returns an [Area] equal to [Long] number of ares.
 */
public inline val Long.ares: Area get() = toArea(AreaUnit.Metric.Are)

/**
 * Returns an [Area] equal to [Double] number of ares. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.ares: Area get() = toArea(AreaUnit.Metric.Are)

/**
 * Returns an [Area] equal to [Int] number of decares.
 */
public inline val Int.decares: Area get() = toArea(AreaUnit.Metric.Decare)

/**
 * Returns an [Area] equal to [Long] number of decares.
 */
public inline val Long.decares: Area get() = toArea(AreaUnit.Metric.Decare)

/**
 * Returns an [Area] equal to [Double] number of decares. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.decares: Area get() = toArea(AreaUnit.Metric.Decare)

/**
 * Returns an [Area] equal to [Int] number of hectares.
 */
public inline val Int.hectares: Area get() = toArea(AreaUnit.Metric.Hectare)

/**
 * Returns an [Area] equal to [Long] number of hectares.
 */
public inline val Long.hectares: Area get() = toArea(AreaUnit.Metric.Hectare)

/**
 * Returns an [Area] equal to [Double] number of hectares. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.hectares: Area get() = toArea(AreaUnit.Metric.Hectare)

/**
 * Returns an [Area] equal to [Int] number of the specified [unit].
 */
public fun Int.toArea(unit: AreaUnit): Area = toLong().toArea(unit)

/**
 * Returns an [Area] equal to [Long] number of the specified [unit].
 */
public fun Long.toArea(unit: AreaUnit): Area = Area(saturated * unit.millimetersSquaredScale)

/**
 * Returns an [Area] equal to [Double] number of the specified [unit]. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public fun Double.toArea(unit: AreaUnit): Area {
    val valueInMillimeters2 = this * unit.millimetersSquaredScale
    require(!valueInMillimeters2.isNaN()) { "Area value cannot be NaN." }
    return Area(valueInMillimeters2.roundToLong().saturated)
}

internal inline val Long.mm2: Area get() = Area(saturated)
internal inline val Int.mm2: Area get() = Area(toLong().saturated)
internal inline val SaturatingLong.mm2: Area get() = Area(this)


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/energy.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Energy
import io.github.kevincianfarini.alchemist.unit.EnergyUnit
import kotlin.math.roundToLong

/**
 * Returns an [Energy] equal to [Int] number of millijoules.
 */
public inline val Int.millijoules: Energy get() = toEnergy(EnergyUnit.International.Millijoule)

/**
 * Returns an [Energy] equal to [Long] number of millijoules.
 */
public inline val Long.millijoules: Energy get() = toEnergy(EnergyUnit.International.Millijoule)
internal inline val SaturatingLong.millijoules get() = Energy(this)

/**
 * Returns an [Energy] equal to [Int] number of joules.
 */
public inline val Int.joules: Energy get() = toEnergy(EnergyUnit.International.Joule)

/**
 * Returns an [Energy] equal to [Long] number of joules.
 */
public inline val Long.joules: Energy get() = toEnergy(EnergyUnit.International.Joule)

/**
 * Returns an [Energy] equal to [Double] number of joules. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.joules: Energy get() = toEnergy(EnergyUnit.International.Joule)

/**
 * Returns an [Energy] equal to [Int] number of kilojoules.
 */
public inline val Int.kilojoules: Energy get() = toEnergy(EnergyUnit.International.Kilojoule)

/**
 * Returns an [Energy] equal to [Long] number of kilojoules.
 */
public inline val Long.kilojoules: Energy get() = toEnergy(EnergyUnit.International.Kilojoule)

/**
 * Returns an [Energy] equal to [Double] number of kilojoules. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.kilojoules: Energy get() = toEnergy(EnergyUnit.International.Kilojoule)

/**
 * Returns an [Energy] equal to [Int] number of megajoules.
 */
public inline val Int.megajoules: Energy get() = toEnergy(EnergyUnit.International.Megajoule)

/**
 * Returns an [Energy] equal to [Long] number of megajoules.
 */
public inline val Long.megajoules: Energy get() = toEnergy(EnergyUnit.International.Megajoule)

/**
 * Returns an [Energy] equal to [Double] number of megajoules. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.megajoules: Energy get() = toEnergy(EnergyUnit.International.Megajoule)

/**
 * Returns an [Energy] equal to [Int] number of gigajoules.
 */
public inline val Int.gigajoules: Energy get() = toEnergy(EnergyUnit.International.Gigajoule)

/**
 * Returns an [Energy] equal to [Long] number of gigajoules.
 */
public inline val Long.gigajoules: Energy get() = toEnergy(EnergyUnit.International.Gigajoule)

/**
 * Returns an [Energy] equal to [Double] number of gigajoules. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.gigajoules: Energy get() = toEnergy(EnergyUnit.International.Gigajoule)

/**
 * Returns an [Energy] equal to [Int] number of tetrajoules.
 */
public inline val Int.tetrajoules: Energy get() = toEnergy(EnergyUnit.International.Tetrajoule)

/**
 * Returns an [Energy] equal to [Long] number of tetrajoules.
 */
public inline val Long.tetrajoules: Energy get() = toEnergy(EnergyUnit.International.Tetrajoule)

/**
 * Returns an [Energy] equal to [Double] number of tetrajoules. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.tetrajoules: Energy get() = toEnergy(EnergyUnit.International.Tetrajoule)

/**
 * Returns an [Energy] equal to [Int] number of petajoules.
 */
public inline val Int.petajoules: Energy get() = toEnergy(EnergyUnit.International.Petajoule)

/**
 * Returns an [Energy] equal to [Long] number of petajoules.
 */
public inline val Long.petajoules: Energy get() = toEnergy(EnergyUnit.International.Petajoule)

/**
 * Returns an [Energy] equal to [Double] number of petajoules. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.petajoules: Energy get() = toEnergy(EnergyUnit.International.Petajoule)

/**
 * Returns an [Energy] equal to [Int] number of milliwatt-hours.
 */
public inline val Int.milliwattHours: Energy get() = toEnergy(EnergyUnit.Electricity.MilliwattHour)

/**
 * Returns an [Energy] equal to [Long] number of milliwatt-hours.
 */
public inline val Long.milliwattHours: Energy get() = toEnergy(EnergyUnit.Electricity.MilliwattHour)

/**
 * Returns an [Energy] equal to [Double] number of milliwatt-hours. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.milliwattHours: Energy get() = toEnergy(EnergyUnit.Electricity.MilliwattHour)

/**
 * Returns an [Energy] equal to [Int] number of watt-hours.
 */
public inline val Int.wattHours: Energy get() = toEnergy(EnergyUnit.Electricity.WattHour)

/**
 * Returns an [Energy] equal to [Long] number of watt-hours.
 */
public inline val Long.wattHours: Energy get() = toEnergy(EnergyUnit.Electricity.WattHour)

/**
 * Returns an [Energy] equal to [Double] number of watt-hours. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.wattHours: Energy get() = toEnergy(EnergyUnit.Electricity.WattHour)

/**
 * Returns an [Energy] equal to [Int] number of kilowatt-hours.
 */
public inline val Int.kilowattHours: Energy get() = toEnergy(EnergyUnit.Electricity.KilowattHour)

/**
 * Returns an [Energy] equal to [Long] number of kilowatt-hours.
 */
public inline val Long.kilowattHours: Energy get() = toEnergy(EnergyUnit.Electricity.KilowattHour)

/**
 * Returns an [Energy] equal to [Double] number of kilowatt-hours. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.kilowattHours: Energy get() = toEnergy(EnergyUnit.Electricity.KilowattHour)

/**
 * Returns an [Energy] equal to [Int] number of megawatt-hours.
 */
public inline val Int.megawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.MegawattHour)

/**
 * Returns an [Energy] equal to [Long] number of megawatt-hours.
 */
public inline val Long.megawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.MegawattHour)

/**
 * Returns an [Energy] equal to [Double] number of megawatt-hours. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.megawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.MegawattHour)

/**
 * Returns an [Energy] equal to [Int] number of gigawatt-hours.
 */
public inline val Int.gigawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.GigawattHour)

/**
 * Returns an [Energy] equal to [Long] number of gigawatt-hours.
 */
public inline val Long.gigawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.GigawattHour)

/**
 * Returns an [Energy] equal to [Double] number of gigawatt-hours. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.gigawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.GigawattHour)

/**
 * Returns an [Energy] equal to [Int] number of terawatt-hours.
 */
public inline val Int.terawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.TerawattHour)

/**
 * Returns an [Energy] equal to [Int] number of terawatt-hours.
 */
public inline val Long.terawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.TerawattHour)

/**
 * Returns an [Energy] equal to [Double] number of terawatt-hours. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.terawattHours: Energy get() = toEnergy(EnergyUnit.Electricity.TerawattHour)

/**
 * Returns an [Energy] equal to [Int] number of the specified [unit].
 */
public fun Int.toEnergy(unit: EnergyUnit): Energy {
    return toLong().toEnergy(unit)
}

/**
 * Returns an [Energy] equal to [Long] number of the specified [unit].
 */
public fun Long.toEnergy(unit: EnergyUnit): Energy {
    return Energy(this.saturated * unit.millijouleScale)
}

/**
 * Returns an [Energy] equal to [Double] number of the specified [unit]. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public fun Double.toEnergy(unit: EnergyUnit): Energy {
    val valueInMillijoules = this * unit.millijouleScale
    require(!valueInMillijoules.isNaN()) { "Energy value cannot be NaN." }
    return Energy(valueInMillijoules.roundToLong().saturated)
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/force.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Force
import io.github.kevincianfarini.alchemist.unit.ForceUnit
import kotlin.math.roundToLong

/**
 * Returns an [Force] equal to [Int] number of nanonewtons.
 */
public inline val Int.nanonewtons: Force get() = toForce(ForceUnit.International.Nanonewton)

/**
 * Returns an [Force] equal to [Long] number of nanonewtons.
 */
public inline val Long.nanonewtons: Force get() = toForce(ForceUnit.International.Nanonewton)

/**
 * Returns an [Force] equal to [Int] number of micronewtons.
 */
public inline val Int.micronewtons: Force get() = toForce(ForceUnit.International.Micronewton)

/**
 * Returns an [Force] equal to [Long] number of micronewtons.
 */
public inline val Long.micronewtons: Force get() = toForce(ForceUnit.International.Micronewton)

/**
 * Returns an [Force] equal to [Double] number of micronewtons. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.micronewtons: Force get() = toForce(ForceUnit.International.Micronewton)

/**
 * Returns an [Force] equal to [Int] number of millinewtons.
 */
public inline val Int.millinewtons: Force get() = toForce(ForceUnit.International.Millinewton)

/**
 * Returns an [Force] equal to [Long] number of millinewtons.
 */
public inline val Long.millinewtons: Force get() = toForce(ForceUnit.International.Millinewton)

/**
 * Returns an [Force] equal to [Double] number of millinewtons. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.millinewtons: Force get() = toForce(ForceUnit.International.Millinewton)

/**
 * Returns an [Force] equal to [Int] number of newtons.
 */
public inline val Int.newtons: Force get() = toForce(ForceUnit.International.Newton)

/**
 * Returns an [Force] equal to [Long] number of newtons.
 */
public inline val Long.newtons: Force get() = toForce(ForceUnit.International.Newton)

/**
 * Returns an [Force] equal to [Double] number of newtons. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.newtons: Force get() = toForce(ForceUnit.International.Newton)

/**
 * Returns an [Force] equal to [Int] number of kilonewtons.
 */
public inline val Int.kilonewtons: Force get() = toForce(ForceUnit.International.Kilonewton)

/**
 * Returns an [Force] equal to [Long] number of kilonewtons.
 */
public inline val Long.kilonewtons: Force get() = toForce(ForceUnit.International.Kilonewton)

/**
 * Returns an [Force] equal to [Double] number of kilonewtons. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.kilonewtons: Force get() = toForce(ForceUnit.International.Kilonewton)

/**
 * Returns an [Force] equal to [Int] number of meganewtons.
 */
public inline val Int.meganewtons: Force get() = toForce(ForceUnit.International.Meganewton)

/**
 * Returns an [Force] equal to [Long] number of meganewtons.
 */
public inline val Long.meganewtons: Force get() = toForce(ForceUnit.International.Meganewton)

/**
 * Returns an [Force] equal to [Double] number of meganewtons. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.meganewtons: Force get() = toForce(ForceUnit.International.Meganewton)

/**
 * Returns an [Force] equal to [Int] number of giganewtons.
 */
public inline val Int.giganewtons: Force get() = toForce(ForceUnit.International.Giganewton)

/**
 * Returns an [Force] equal to [Long] number of giganewtons.
 */
public inline val Long.giganewtons: Force get() = toForce(ForceUnit.International.Giganewton)

/**
 * Returns an [Force] equal to [Double] number of giganewtons. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.giganewtons: Force get() = toForce(ForceUnit.International.Giganewton)

/**
 * Returns an [Force] equal to [Int] number of the specified [unit].
 */
public fun Int.toForce(unit: ForceUnit): Force = toLong().toForce(unit)

/**
 * Returns an [Force] equal to [Long] number of the specified [unit].
 */
public fun Long.toForce(unit: ForceUnit): Force = Force(saturated * unit.nanonewtonScale)

/**
 * Returns an [Force] equal to [Double] number of the specified [unit]. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public fun Double.toForce(unit: ForceUnit): Force {
    val valueInNanonewtons = this * unit.nanonewtonScale
    require(!valueInNanonewtons.isNaN()) { "Force value cannot be NaN." }
    return Force(valueInNanonewtons.roundToLong().saturated)
}

internal inline val SaturatingLong.kilonewtons: Force get() = rawValue.toForce(ForceUnit.International.Kilonewton)

================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/length.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Length
import io.github.kevincianfarini.alchemist.unit.LengthUnit
import kotlin.math.roundToLong


/**
 * Returns a [Length] equal to [Int] number of nanometers.
 */
public inline val Int.nanometers: Length get() = toLength(LengthUnit.International.Nanometer)

/**
 * Returns a [Length] equal to [Long] number of nanometers.
 */
public inline val Long.nanometers: Length get() = toLength(LengthUnit.International.Nanometer)

internal inline val SaturatingLong.nanometers: Length get() = Length(this)

/**
 * Returns a [Length] equal to [Int] number of micrometers.
 */
public inline val Int.micrometers: Length get() = toLength(LengthUnit.International.Micrometer)

/**
 * Returns a [Length] equal to [Long] number of micrometers.
 */
public inline val Long.micrometers: Length get() = toLength(LengthUnit.International.Micrometer)

/**
 * Returns a [Length] equal to [Double] number of micrometers. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.micrometers: Length get() = toLength(LengthUnit.International.Micrometer)

/**
 * Returns a [Length] equal to [Int] number of millimeters.
 */
public inline val Int.millimeters: Length get() = toLength(LengthUnit.International.Millimeter)

/**
 * Returns a [Length] equal to [Long] number of millimeters.
 */
public inline val Long.millimeters: Length get() = toLength(LengthUnit.International.Millimeter)

/**
 * Returns a [Length] equal to [Double] number of millimeters. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.millimeters: Length get() = toLength(LengthUnit.International.Millimeter)

/**
 * Returns a [Length] equal to [Int] number of centimeters.
 */
public inline val Int.centimeters: Length get() = toLength(LengthUnit.International.Centimeter)

/**
 * Returns a [Length] equal to [Long] number of centimeters.
 */
public inline val Long.centimeters: Length get() = toLength(LengthUnit.International.Centimeter)

internal inline val SaturatingLong.centimeters: Length get() = rawValue.toLength(LengthUnit.International.Centimeter)

/**
 * Returns a [Length] equal to [Double] number of centimeters. Depending on its magnitude, some precision may be lost.
 */
public inline val Double.centimeters: Length get() = toLength(LengthUnit.International.Centimeter)

/**
 * Returns a [Length] equal to [Int] number of meters.
 */
public inline val Int.meters: Length get() = toLength(LengthUnit.International.Meter)

/**
 * Returns a [Length] equal to [Long] number of meters.
 */
public inline val Long.meters: Length get() = toLength(LengthUnit.International.Meter)

/**
 * Returns a [Length] equal to [Double] number of meters. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.meters: Length get() = toLength(LengthUnit.International.Meter)

/**
 * Returns a [Length] equal to [Int] number of kilometers.
 */
public inline val Int.kilometers: Length get() = toLength(LengthUnit.International.Kilometer)

/**
 * Returns a [Length] equal to [Long] number of kilometers.
 */
public inline val Long.kilometers: Length get() = toLength(LengthUnit.International.Kilometer)

/**
 * Returns a [Length] equal to [Double] number of kilometers. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.kilometers: Length get() = toLength(LengthUnit.International.Kilometer)

/**
 * Returns a [Length] equal to [Int] number of megameters.
 */
public inline val Int.megameters: Length get() = toLength(LengthUnit.International.Megameter)

/**
 * Returns a [Length] equal to [Long] number of megameters.
 */
public inline val Long.megameters: Length get() = toLength(LengthUnit.International.Megameter)

/**
 * Returns a [Length] equal to [Double] number of megameters. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.megameters: Length get() = toLength(LengthUnit.International.Megameter)

/**
 * Returns a [Length] equal to [Int] number of gigameters.
 */
public inline val Int.gigameters: Length get() = toLength(LengthUnit.International.Gigameter)

/**
 * Returns a [Length] equal to [Long] number of gigameters.
 */
public inline val Long.gigameters: Length get() = toLength(LengthUnit.International.Gigameter)

/**
 * Returns a [Length] equal to [Double] number of gigameters. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.gigameters: Length get() = toLength(LengthUnit.International.Gigameter)

/**
 * Returns a [Length] equal to [Int] number of inches.
 */
public inline val Int.inches: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Inch)

/**
 * Returns a [Length] equal to [Long] number of inches.
 */
public inline val Long.inches: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Inch)

/**
 * Returns a [Length] equal to [Double] number of inches. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.inches: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Inch)

/**
 * Returns a [Length] equal to [Int] number of feet.
 */
public inline val Int.feet: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Foot)

/**
 * Returns a [Length] equal to [Long] number of feet.
 */
public inline val Long.feet: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Foot)

/**
 * Returns a [Length] equal to [Double] number of feet. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.feet: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Foot)

/**
 * Returns a [Length] equal to [Int] number of yards.
 */
public inline val Int.yards: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Yard)

/**
 * Returns a [Length] equal to [Long] number of yards.
 */
public inline val Long.yards: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Yard)

/**
 * Returns a [Length] equal to [Double] number of yards. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.yards: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Yard)

/**
 * Returns a [Length] equal to [Int] number of miles.
 */
public inline val Int.miles: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Mile)

/**
 * Returns a [Length] equal to [Long] number of miles.
 */
public inline val Long.miles: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Mile)

/**
 * Returns a [Length] equal to [Double] number of miles. Depending on its magnitude, some precision may be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.miles: Length get() = toLength(LengthUnit.UnitedStatesCustomary.Mile)

/**
 * Returns a [Length] equal to [Int] number of the specified [unit].
 */
public fun Int.toLength(unit: LengthUnit): Length {
    return toLong().toLength(unit)
}

/**
 * Returns a [Length] equal to [Long] number of the specified [unit].
 */
public fun Long.toLength(unit: LengthUnit): Length {
    return Length(this.saturated * unit.nanometerScale)
}

/**
 * Returns a [Length] equal to [Double] number of the specified [unit]. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public fun Double.toLength(unit: LengthUnit): Length {
    val valueInNanometers = this * unit.nanometerScale
    require(!valueInNanometers.isNaN()) { "Length value cannot be NaN." }
    return Length(valueInNanometers.roundToLong().saturated)
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/mass.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Mass
import io.github.kevincianfarini.alchemist.unit.MassUnit
import kotlin.math.roundToLong

/**
 * Returns a [Mass] equal to [Int] number of micrograms.
 */
public inline val Int.micrograms: Mass get() = toMass(MassUnit.International.Microgram)

/**
 * Returns a [Mass] equal to [Long] number of micrograms.
 */
public inline val Long.micrograms: Mass get() = toMass(MassUnit.International.Microgram)

/**
 * Returns a [Mass] equal to [Int] number of milligrams.
 */
public inline val Int.milligrams: Mass get() = toMass(MassUnit.International.Milligram)

/**
 * Returns a [Mass] equal to [Long] number of milligrams.
 */
public inline val Long.milligrams: Mass get() = toMass(MassUnit.International.Milligram)

/**
 * Returns a [Mass] equal to [Double] number of milligrams. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.milligrams: Mass get() = toMass(MassUnit.International.Milligram)

/**
 * Returns a [Mass] equal to [Int] number of grams.
 */
public inline val Int.grams: Mass get() = toMass(MassUnit.International.Gram)

/**
 * Returns a [Mass] equal to [Long] number of grams.
 */
public inline val Long.grams: Mass get() = toMass(MassUnit.International.Gram)

/**
 * Returns a [Mass] equal to [Double] number of grams. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.grams: Mass get() = toMass(MassUnit.International.Gram)

/**
 * Returns a [Mass] equal to [Int] number of kilograms.
 */
public inline val Int.kilograms: Mass get() = toMass(MassUnit.International.Kilogram)

/**
 * Returns a [Mass] equal to [Long] number of kilograms.
 */
public inline val Long.kilograms: Mass get() = toMass(MassUnit.International.Kilogram)

/**
 * Returns a [Mass] equal to [Double] number of kilograms. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.kilograms: Mass get() = toMass(MassUnit.International.Kilogram)

/**
 * Returns a [Mass] equal to [Int] number of megagrams.
 */
public inline val Int.megagrams: Mass get() = toMass(MassUnit.International.Megagram)

/**
 * Returns a [Mass] equal to [Long] number of megagrams.
 */
public inline val Long.megagrams: Mass get() = toMass(MassUnit.International.Megagram)

/**
 * Returns a [Mass] equal to [Double] number of megagrams. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.megagrams: Mass get() = toMass(MassUnit.International.Megagram)

/**
 * Returns a [Mass] equal to [Int] number of metric tonnes.
 */
public inline val Int.metricTonnes: Mass get() = toMass(MassUnit.International.Megagram)

/**
 * Returns a [Mass] equal to [Long] number of metric tonnes.
 */
public inline val Long.metricTonnes: Mass get() = toMass(MassUnit.International.Megagram)

/**
 * Returns a [Mass] equal to [Double] number of metric tonnes. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.metricTonnes: Mass get() = toMass(MassUnit.International.Megagram)

/**
 * Returns a [Mass] equal to [Int] number of gigagrams.
 */
public inline val Int.gigagrams: Mass get() = toMass(MassUnit.International.Gigagram)

/**
 * Returns a [Mass] equal to [Long] number of gigagrams.
 */
public inline val Long.gigagrams: Mass get() = toMass(MassUnit.International.Gigagram)

/**
 * Returns a [Mass] equal to [Double] number of gigagrams. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.gigagrams: Mass get() = toMass(MassUnit.International.Gigagram)

/**
 * Returns a [Mass] equal to [Int] number of teragrams.
 */
public inline val Int.teragrams: Mass get() = toMass(MassUnit.International.Teragram)

/**
 * Returns a [Mass] equal to [Long] number of teragrams.
 */
public inline val Long.teragrams: Mass get() = toMass(MassUnit.International.Teragram)

/**
 * Returns a [Mass] equal to [Double] number of teragrams. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.teragrams: Mass get() = toMass(MassUnit.International.Teragram)

/**
 * Returns a [Mass] equal to [Int] number of the specified [unit].
 */
public fun Int.toMass(unit: MassUnit): Mass {
    return toLong().toMass(unit)
}

/**
 * Returns a [Mass] equal to [Long] number of the specified [unit].
 */
public fun Long.toMass(unit: MassUnit): Mass {
    return Mass(saturated * unit.microgramScale)
}

/**
 * Returns a [Mass] equal to [Double] number of the specified [unit]. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public fun Double.toMass(unit: MassUnit): Mass {
    val valueInMicrograms = this * unit.microgramScale
    require(!valueInMicrograms.isNaN()) { "Mass value cannot be NaN." }
    return Mass(valueInMicrograms.roundToLong().saturated)
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/power.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Power
import io.github.kevincianfarini.alchemist.unit.PowerUnit
import kotlin.math.roundToLong

/**
 * Returns a [Power] equal to [Int] number of terawatts.
 */
public inline val Int.terawatts: Power get() = toPower(PowerUnit.International.Terawatt)

/**
 * Returns a [Power] equal to [Long] number of terawatts.
 */
public inline val Long.terawatts: Power get() = toPower(PowerUnit.International.Terawatt)

/**
 * Returns a [Power] equal to [Double] number of terawatts. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.terawatts: Power get() = toPower(PowerUnit.International.Terawatt)

/**
 * Returns a [Power] equal to [Int] number of gigawatts.
 */
public inline val Int.gigawatts: Power get() = toPower(PowerUnit.International.Gigawatt)

/**
 * Returns a [Power] equal to [Long] number of gigawatts.
 */
public inline val Long.gigawatts: Power get() = toPower(PowerUnit.International.Gigawatt)

/**
 * Returns a [Power] equal to [Double] number of gigawatts. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.gigawatts: Power get() = toPower(PowerUnit.International.Gigawatt)

/**
 * Returns a [Power] equal to [Int] number of megawatts.
 */
public inline val Int.megawatts: Power get() = toPower(PowerUnit.International.Megawatt)

/**
 * Returns a [Power] equal to [Long] number of megawatts.
 */
public inline val Long.megawatts: Power get() = toPower(PowerUnit.International.Megawatt)

/**
 * Returns a [Power] equal to [Double] number of megawatts. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.megawatts: Power get() = toPower(PowerUnit.International.Megawatt)

/**
 * Returns a [Power] equal to [Int] number of kilowatts.
 */
public inline val Int.kilowatts: Power get() = toPower(PowerUnit.International.Kilowatt)

/**
 * Returns a [Power] equal to [Long] number of kilowatts.
 */
public inline val Long.kilowatts: Power get() = toPower(PowerUnit.International.Kilowatt)

/**
 * Returns a [Power] equal to [Double] number of kilowatts. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.kilowatts: Power get() = toPower(PowerUnit.International.Kilowatt)

/**
 * Returns a [Power] equal to [Int] number of watts.
 */
public inline val Int.watts: Power get() = toPower(PowerUnit.International.Watt)

/**
 * Returns a [Power] equal to [Long] number of watts.
 */
public inline val Long.watts: Power get() = toPower(PowerUnit.International.Watt)

/**
 * Returns a [Power] equal to [Double] number of watts. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.watts: Power get() = toPower(PowerUnit.International.Watt)

/**
 * Returns a [Power] equal to [Int] number of milliwatts.
 */
public inline val Int.milliwatts: Power get() = toPower(PowerUnit.International.Milliwatt)

/**
 * Returns a [Power] equal to [Long] number of milliwatts.
 */
public inline val Long.milliwatts: Power get() = toPower(PowerUnit.International.Milliwatt)

/**
 * Returns a [Power] equal to [Double] number of milliwatts. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.milliwatts: Power get() = toPower(PowerUnit.International.Milliwatt)

/**
 * Returns a [Power] equal to [Int] number of microwatts.
 */
public inline val Int.microwatts: Power get() = toPower(PowerUnit.International.Microwatt)

/**
 * Returns a [Power] equal to [Long] number of microwatts.
 */
public inline val Long.microwatts: Power get() = toPower(PowerUnit.International.Microwatt)

internal inline val SaturatingLong.megawatts get() = rawValue.toPower(PowerUnit.International.Megawatt)
internal inline val SaturatingLong.kilowatts get() = rawValue.toPower(PowerUnit.International.Kilowatt)
internal inline val SaturatingLong.watts get() = rawValue.toPower(PowerUnit.International.Watt)
internal inline val SaturatingLong.milliwatts get() = rawValue.toPower(PowerUnit.International.Milliwatt)
internal inline val SaturatingLong.microwatts get() = rawValue.toPower(PowerUnit.International.Microwatt)

/**
 * Returns a [Power] equal to [Int] number of the specified [unit].
 */
public fun Int.toPower(unit: PowerUnit): Power {
    return toLong().toPower(unit)
}

/**
 * Returns a [Power] equal to [Long] number of the specified [unit].
 */
public fun Long.toPower(unit: PowerUnit): Power {
    return Power(saturated * unit.microwattScale)
}

/**
 * Returns a [Power] equal to [Double] number of the specified [unit]. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public fun Double.toPower(unit: PowerUnit): Power {
    val valueInMicrowatts = this * unit.microwattScale
    require(!valueInMicrowatts.isNaN()) { "Mass value cannot be NaN." }
    return Power(valueInMicrowatts.roundToLong().saturated)
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/temperature.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Temperature
import io.github.kevincianfarini.alchemist.unit.TemperatureUnit
import io.github.kevincianfarini.alchemist.unit.convertToNanokelvin

/**
 * Returns a [Temperature] equal to [Int] number of nanokelvins.
 */
public val Int.nanokelvins: Temperature get() = toLong().nanokelvins

/**
 * Returns a [Temperature] equal to [Long] number of nanokelvins.
 */
public val Long.nanokelvins: Temperature get() = saturated.nanokelvins

/**
 * Returns a [Temperature] equal to [Int] number of microkelvins.
 */
public val Int.microkelvins: Temperature get() = toLong().microkelvins

/**
 * Returns a [Temperature] equal to [Long] number of microkelvins.
 */
public val Long.microkelvins: Temperature get() = saturated.microkelvins

/**
 * Returns a [Temperature] equal to [Double] number of microkelvins. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public val Double.microkelvins: Temperature get() = Temperature(
    TemperatureUnit.International.Microkelvin.convertToNanokelvin(this).saturated
)

/**
 * Returns a [Temperature] equal to [Int] number of millikelvins.
 */
public val Int.millikelvins: Temperature get() = toLong().millikelvins

/**
 * Returns a [Temperature] equal to [Long] number of millikelvins.
 */
public val Long.millikelvins: Temperature get() = saturated.millikelvins

/**
 * Returns a [Temperature] equal to [Double] number of millikelvins. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public val Double.millikelvins: Temperature get() = Temperature(
    TemperatureUnit.International.Millikelvin.convertToNanokelvin(this).saturated
)

/**
 * Returns a [Temperature] equal to [Int] number of kelvins.
 */
public val Int.kelvins: Temperature get() = toLong().kelvins

/**
 * Returns a [Temperature] equal to [Long] number of kelvins.
 */
public val Long.kelvins: Temperature get() = saturated.kelvins

/**
 * Returns a [Temperature] equal to [Double] number of kelvins. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public val Double.kelvins: Temperature get() = Temperature(
    TemperatureUnit.International.Kelvin.convertToNanokelvin(this).saturated
)

/**
 * Returns a [Temperature] equal to [Int] number of kilokelvins.
 */
public val Int.kilokelvins: Temperature get() = toLong().kilokelvins

/**
 * Returns a [Temperature] equal to [Long] number of kilokelvins.
 */
public val Long.kilokelvins: Temperature get() = saturated.kilokelvins

/**
 * Returns a [Temperature] equal to [Double] number of kilokelvins. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public val Double.kilokelvins: Temperature get() = Temperature(
    TemperatureUnit.International.Kilokelvin.convertToNanokelvin(this).saturated
)

/**
 * Returns a [Temperature] equal to [Int] number of megakelvins.
 */
public val Int.megakelvins: Temperature get() = toLong().megakelvins

/**
 * Returns a [Temperature] equal to [Long] number of megakelvins.
 */
public val Long.megakelvins: Temperature get() = saturated.megakelvins

/**
 * Returns a [Temperature] equal to [Double] number of megakelvins. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public val Double.megakelvins: Temperature get() = Temperature(
    TemperatureUnit.International.Megakelvin.convertToNanokelvin(this).saturated
)

/**
 * Returns a [Temperature] equal to [Int] number of gigakelvins.
 */
public val Int.gigakelvins: Temperature get() = toLong().gigakelvins

/**
 * Returns a [Temperature] equal to [Long] number of gigakelvins.
 */
public val Long.gigakelvins: Temperature get() = saturated.gigakelvins

/**
 * Returns a [Temperature] equal to [Double] number of gigakelvins. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public val Double.gigakelvins: Temperature get() = Temperature(
    TemperatureUnit.International.Gigakelvin.convertToNanokelvin(this).saturated
)

/**
 * Returns a [Temperature] equal to [Int] number of Celsius.
 */
public val Int.celsius: Temperature get() = toLong().celsius

/**
 * Returns a [Temperature] equal to [Long] number of Celsius.
 */
public val Long.celsius: Temperature get() = saturated.celsius

/**
 * Returns a [Temperature] equal to [Double] number of Celsius. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public val Double.celsius: Temperature get() = Temperature(
    TemperatureUnit.International.Celsius.convertToNanokelvin(this).saturated
)

/**
 * Returns a [Temperature] equal to [Int] number of Fahrenheit.
 */
public val Int.fahrenheit: Temperature get() = toLong().fahrenheit

/**
 * Returns a [Temperature] equal to [Long] number of Fahrenheit.
 */
public val Long.fahrenheit: Temperature get() = saturated.fahrenheit

/**
 * Returns a [Temperature] equal to [Double] number of Fahrenheit. Depending on its magnitude, some precision may be
 * lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public val Double.fahrenheit: Temperature get() = Temperature(
    TemperatureUnit.Fahrenheit.convertToNanokelvin(this).saturated
)

private inline val SaturatingLong.microkelvins get() = Temperature(
    TemperatureUnit.International.Microkelvin.convertToNanokelvin(this)
)
private inline val SaturatingLong.nanokelvins get() = Temperature(
    TemperatureUnit.International.Nanokelvin.convertToNanokelvin(this)
)
private inline val SaturatingLong.millikelvins get() = Temperature(
    TemperatureUnit.International.Millikelvin.convertToNanokelvin(this)
)
private inline val SaturatingLong.kelvins get() = Temperature(
    TemperatureUnit.International.Kelvin.convertToNanokelvin(this)
)
private inline val SaturatingLong.kilokelvins get() = Temperature(
    TemperatureUnit.International.Kilokelvin.convertToNanokelvin(this)
)
private inline val SaturatingLong.megakelvins get() = Temperature(
    TemperatureUnit.International.Megakelvin.convertToNanokelvin(this)
)
private inline val SaturatingLong.gigakelvins get() = Temperature(
    TemperatureUnit.International.Gigakelvin.convertToNanokelvin(this)
)
private inline val SaturatingLong.celsius get() = Temperature(
    TemperatureUnit.International.Celsius.convertToNanokelvin(this)
)
private inline val SaturatingLong.fahrenheit get() = Temperature(
    TemperatureUnit.Fahrenheit.convertToNanokelvin(this)
)


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/velocity.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Velocity

internal val Int.nmPerSecond: Velocity get() = Velocity(toLong().saturated)
internal val Long.nmPerSecond: Velocity get() = Velocity(saturated)
internal val SaturatingLong.nmPerSecond: Velocity get() = Velocity(this)


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/volume.kt
================================================
package io.github.kevincianfarini.alchemist.scalar

import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.type.Volume
import io.github.kevincianfarini.alchemist.unit.VolumeUnit
import kotlin.math.roundToLong

/**
 * Returns a [Volume] equal to [Int] number of milliliters.
 */
public inline val Int.milliliters: Volume get() = toVolume(VolumeUnit.Metric.Milliliter)

/**
 * Returns a [Volume] equal to [Long] number of milliliters.
 */
public inline val Long.milliliters: Volume get() = toVolume(VolumeUnit.Metric.Milliliter)

/**
 * Returns a [Volume] equal to [Int] number of liters.
 */
public inline val Int.liters: Volume get() = toVolume(VolumeUnit.Metric.Liter)

/**
 * Returns a [Volume] equal to [Long] number of liters.
 */
public inline val Long.liters: Volume get() = toVolume(VolumeUnit.Metric.Liter)

/**
 * Returns a [Volume] equal to [Double] number of liters. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.liters: Volume get() = toVolume(VolumeUnit.Metric.Liter)

/**
 * Returns a [Volume] equal to [Int] number of kiloliters.
 */
public inline val Int.kiloliters: Volume get() = toVolume(VolumeUnit.Metric.Kiloliter)

/**
 * Returns a [Volume] equal to [Long] number of kiloliters.
 */
public inline val Long.kiloliters: Volume get() = toVolume(VolumeUnit.Metric.Kiloliter)

/**
 * Returns a [Volume] equal to [Double] number of kiloliters. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.kiloliters: Volume get() = toVolume(VolumeUnit.Metric.Kiloliter)

/**
 * Returns a [Volume] equal to [Int] number of megaliters.
 */
public inline val Int.megaliters: Volume get() = toVolume(VolumeUnit.Metric.Megaliter)

/**
 * Returns a [Volume] equal to [Long] number of megaliters.
 */
public inline val Long.megaliters: Volume get() = toVolume(VolumeUnit.Metric.Megaliter)

/**
 * Returns a [Volume] equal to [Double] number of megaliters. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.megaliters: Volume get() = toVolume(VolumeUnit.Metric.Megaliter)

/**
 * Returns a [Volume] equal to [Int] number of gigaliters.
 */
public inline val Int.gigaliters: Volume get() = toVolume(VolumeUnit.Metric.Gigaliter)

/**
 * Returns a [Volume] equal to [Long] number of gigaliters.
 */
public inline val Long.gigaliters: Volume get() = toVolume(VolumeUnit.Metric.Gigaliter)

/**
 * Returns a [Volume] equal to [Double] number of gigaliters. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.gigaliters: Volume get() = toVolume(VolumeUnit.Metric.Gigaliter)

/**
 * Returns a [Volume] equal to [Int] number of teraliters.
 */
public inline val Int.teraliters: Volume get() = toVolume(VolumeUnit.Metric.Teraliter)

/**
 * Returns a [Volume] equal to [Long] number of teraliters.
 */
public inline val Long.teraliters: Volume get() = toVolume(VolumeUnit.Metric.Teraliter)

/**
 * Returns a [Volume] equal to [Double] number of teraliters. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.teraliters: Volume get() = toVolume(VolumeUnit.Metric.Teraliter)

/**
 * Returns a [Volume] equal to [Int] number of petaliters.
 */
public inline val Int.petaliters: Volume get() = toVolume(VolumeUnit.Metric.Petaliter)

/**
 * Returns a [Volume] equal to [Long] number of petaliters.
 */
public inline val Long.petaliters: Volume get() = toVolume(VolumeUnit.Metric.Petaliter)

/**
 * Returns a [Volume] equal to [Double] number of petaliters. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public inline val Double.petaliters: Volume get() = toVolume(VolumeUnit.Metric.Petaliter)

/**
 * Returns a [Volume] equal to [Int] number of the specified [unit].
 */
public fun Int.toVolume(unit: VolumeUnit): Volume = toLong().toVolume(unit)

/**
 * Returns a [Volume] equal to [Long] number of the specified [unit].
 */
public fun Long.toVolume(unit: VolumeUnit): Volume {
    return Volume(saturated * unit.cubicCentimetersScale)
}

/**
 * Returns a [Volume] equal to [Double] number of the specified [unit]. Depending on its magnitude, some precision may
 * be lost.
 *
 * @throws IllegalArgumentException is this [Double] is [Double.NaN].
 */
public fun Double.toVolume(unit: VolumeUnit): Volume {
    val valueInCubicCentis = this * unit.cubicCentimetersScale
    require(!valueInCubicCentis.isNaN()) { "Volume value cannot be NaN." }
    return Volume(valueInCubicCentis.roundToLong().saturated)
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Acceleration.kt
================================================
package io.github.kevincianfarini.alchemist.type

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.secondScale
import io.github.kevincianfarini.alchemist.internal.shortNameSquared
import io.github.kevincianfarini.alchemist.internal.toDecimalString
import io.github.kevincianfarini.alchemist.scalar.nmPerSecond
import io.github.kevincianfarini.alchemist.unit.LengthUnit
import kotlin.jvm.JvmInline
import kotlin.math.roundToLong
import kotlin.text.Typography.nbsp
import kotlin.time.Duration
import kotlin.time.DurationUnit

/**
 * Represents a measure of acceleration and is capable of storing ±9.2 billion m/s² at nm/s² precision.
 */
@JvmInline
public value class Acceleration internal constructor(
    private val rawNanometersPerSecondSquared: SaturatingLong
) : Comparable<Acceleration> {

    // region SI Arithmetic

    /**
     * Returns the resulting [Velocity] after multiplying this acceleration by the specified [duration].
     *
     * This operation attempts to retain precision, but for sufficiently large values of this acceleration or the
     * specified [duration] some precision may be lost.
     *
     * @throws IllegalArgumentException if this acceleration is infinite and [duration] is zero, or if this acceleration
     * is zero and [duration] is infinite.
     */
    public operator fun times(duration: Duration): Velocity = duration.toComponents { seconds, nanoseconds ->
        val secondComponent = (rawNanometersPerSecondSquared * seconds).nmPerSecond
        val preciseNanosecondComponent = ((rawNanometersPerSecondSquared * nanoseconds) / 1_000_000_000).nmPerSecond
        if (preciseNanosecondComponent.isFinite()) {
            secondComponent + preciseNanosecondComponent
        } else {
            val coarseNanosecondComponent = ((rawNanometersPerSecondSquared / 1_000_000_000) * nanoseconds).nmPerSecond
            secondComponent + coarseNanosecondComponent
        }
    }

    /**
     * Returns the resulting [Force] after multiplying this acceleration by the specified [mass].
     *
     * This operation attempts to retain precision, but for sufficiently large values of this acceleration or the
     * specified [mass] some precision may be lost.
     *
     * @throws IllegalArgumentException if this acceleration is infinite and [mass] is zero, or if this acceleration
     * is zero and [mass] is infinite.
     */
    public operator fun times(mass: Mass): Force {
        return mass.toInternationalComponents { tera, giga, mega, kilo, grams, milli, micro ->
            // Try to find the right level which we can perform this operation at without losing precision.
            // --------------------------------------------------------------------------------------------
            // 1 nm/s² * 1 microgram is 1 attonewton.
            // 1 nm/s² * 1 milligram is 1 femtonewton.
            // 1 nm/s² * 1 gram is 1 piconewton.
            // 1 nm/s² * 1 kilogram is 1 nanonewton.
            // 1 nm/s² * 1 megagram is 1 micronewton.
            // 1 nm/s² * 1 gigagram is 1 millinewton.
            // 1 nm/s² * 1 teragram is 1 newton.
            // --------------------------------------------------------------------------------------------
            val newtons = rawNanometersPerSecondSquared * tera
            val millinewtons = rawNanometersPerSecondSquared * giga
            val micronewtons = rawNanometersPerSecondSquared * mega
            val nanonewtons = rawNanometersPerSecondSquared * kilo
            val piconewtons = rawNanometersPerSecondSquared * grams
            val femtonewtons = rawNanometersPerSecondSquared * milli
            val attonewtons = rawNanometersPerSecondSquared * micro
            // ----------- Try attonewton precision. ------------------------------------------------------
            val attoN = attonewtons + (femtonewtons * 1_000) + (piconewtons * 1_000_000) + (nanonewtons * 1_000_000_000) + (micronewtons * 1_000_000_000_000) + (millinewtons * 1_000_000_000_000_000) + (newtons * 1_000_000_000_000_000_000)
            if (attoN.isFinite()) return@toInternationalComponents Force(attoN / 1_000_000_000)
            // ----------- Try femtonewton precision. ------------------------------------------------------
            val femtoN = (attonewtons / 1_000) + femtonewtons + (piconewtons * 1_000) + (nanonewtons * 1_000_000) + (micronewtons * 1_000_000_000) + (millinewtons * 1_000_000_000_000) + (newtons * 1_000_000_000_000_000)
            if (femtoN.isFinite()) return@toInternationalComponents Force(femtoN / 1_000_000)
            // ----------- Try piconewton precision. ------------------------------------------------------
            val picoN = (attonewtons / 1_000_000) + (femtonewtons / 1_000) + piconewtons + (nanonewtons * 1_000) + (micronewtons * 1_000_000) + (millinewtons * 1_000_000_000) + (newtons * 1_000_000_000_000)
            if (picoN.isFinite()) return@toInternationalComponents Force(picoN / 1_000)
            // ----------- Default nanonewton precision. --------------------------------------------------
            val nanoN = (attonewtons / 1_000_000_000) + (femtonewtons / 1_000_000) + (piconewtons / 1_000) + nanonewtons + (micronewtons * 1_000) + (millinewtons * 1_000_000) + (newtons * 1_000_000_000)
            Force(nanoN)
        }
    }

    // endregion

    // region Scalar Arithmetic

    /**
     * Returns the number that is the ratio of this and the [other] acceleration value.
     *
     * @throws IllegalArgumentException when both this and the [other] acceleration are [infinite][isInfinite].
     */
    public operator fun div(other: Acceleration): Double {
        return rawNanometersPerSecondSquared.toDouble() / other.rawNanometersPerSecondSquared.toDouble()
    }

    /**
     * Returns an acceleration whose value is this acceleration value divided by the specified [scale].
     */
    public operator fun div(scale: Int): Acceleration = div(scale.toLong())

    /**
     * Returns an acceleration whose value is this acceleration value divided by the specified [scale].
     *
     * @throws IllegalArgumentException when [scale] is equal to [Long.MAX_VALUE] or [Long.MIN_VALUE] and this
     * acceleration is [infinite][isInfinite].
     */
    public operator fun div(scale: Long): Acceleration = Acceleration(rawNanometersPerSecondSquared / scale)

    /**
     * Returns the negative of this acceleration value.
     */
    public operator fun unaryMinus(): Acceleration = Acceleration(-rawNanometersPerSecondSquared)

    /**
     * Returns an acceleration whose value is the difference between this and the [other] acceleration value.
     *
     * @throws IllegalArgumentException if this acceleration and the [other] acceleration are both
     * [infinite][isInfinite] but have equivalent signs.
     */
    public operator fun minus(other: Acceleration): Acceleration {
        return Acceleration(rawNanometersPerSecondSquared - other.rawNanometersPerSecondSquared)
    }

    /**
     * Returns an acceleration whose value is the sum between this and the [other] acceleration value.
     *
     * @throws IllegalArgumentException if this acceleration and the [other] acceleration are both
     * [infinite][isInfinite] but have differing signs.
     */
    public operator fun plus(other: Acceleration): Acceleration {
        return Acceleration(rawNanometersPerSecondSquared + other.rawNanometersPerSecondSquared)
    }

    /**
     * Returns an acceleration whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this acceleration is [infinite][isInfinite] and [scale] is 0.
     */
    public operator fun times(scale: Int): Acceleration = times(scale.toLong())

    /**
     * Returns an acceleration whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this acceleration is [infinite][isInfinite] and [scale] is 0, or when this
     * acceleration is 0 and scale is [Long.MAX_VALUE] or [Long.MIN_VALUE].
     */
    public operator fun times(scale: Long): Acceleration = Acceleration(rawNanometersPerSecondSquared * scale)

    /**
     * Returns an acceleration whose value is multiplied by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this acceleration is [infinite][isInfinite] and [scale] is 0.0 or when this acceleration is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun times(scale: Double): Acceleration = Acceleration(rawNanometersPerSecondSquared * scale)

    /**
     * Returns an acceleration whose value is divided by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this acceleration is [infinite][isInfinite] and [scale] is 0.0 or when this acceleration is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun div(scale: Double): Acceleration = Acceleration(rawNanometersPerSecondSquared / scale)

    // endregion

    // region Acceleration to Scalar Conversions

    /**
     * Returns the value of this acceleration expressed as a [Long] number of the specified [lengthUnit] per
     * [durationUnit]². Infinite values are converted to either [Long.MAX_VALUE] or [Long.MIN_VALUE] depending on its
     * sign.
     */
    public fun toLong(lengthUnit: LengthUnit, durationUnit: DurationUnit): Long {
        return toDouble(lengthUnit, durationUnit).roundToLong()
    }

    /**
     * Returns the value of this acceleration expressed as a [Double] number of the specified [lengthUnit] per
     * [durationUnit]². Infinite values are converted to either [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY]
     * depending on its sign.
     */
    public fun toDouble(lengthUnit: LengthUnit, durationUnit: DurationUnit): Double {
        val lengthPerSecond2 = rawNanometersPerSecondSquared.toDouble() / lengthUnit.nanometerScale.toDouble()
        return lengthPerSecond2 * durationUnit.secondScale * durationUnit.secondScale
    }

    /**
     * Returns a fractional string representation of this acceleration expressed in the specified [lengthUnit] per
     * [durationUnit]² and is rounded to the specified [decimals].
     */
    public fun toString(lengthUnit: LengthUnit, durationUnit: DurationUnit, decimals: Int = 0): String {
        return when (isInfinite()) {
            true -> rawNanometersPerSecondSquared.toString()
            false -> buildString {
                append(toDouble(lengthUnit, durationUnit).toDecimalString(decimals))
                append(nbsp)
                append(lengthUnit.symbol)
                append("/")
                append(durationUnit.shortNameSquared)
            }
        }
    }

    /**
     * Returns a fractional string representation of this acceleration expressed in the largest
     * [LengthUnit.International] per second² quantity which is greater than or equal to 1.
     */
    override fun toString(): String {
        val lengthUnit = LengthUnit.International.entries.asReversed().firstOrNull { unit ->
            rawNanometersPerSecondSquared.absoluteValue / unit.nanometerScale > 0
        }
        return toString(lengthUnit ?: LengthUnit.International.Nanometer, DurationUnit.SECONDS, decimals = 2)
    }

    // endregion

    // region Comparisons

    /**
     * Returns true if this acceleration value is finite.
     */
    public fun isFinite(): Boolean = rawNanometersPerSecondSquared.isFinite()

    /**
     * Returns true if this acceleration value is infinite.
     */
    public fun isInfinite(): Boolean = rawNanometersPerSecondSquared.isInfinite()

    /**
     * Compares this acceleration with the [other] acceleration. Returns zero if this acceleration is equal
     * to the specified [other] acceleration, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    override fun compareTo(other: Acceleration): Int {
        return rawNanometersPerSecondSquared.compareTo(other.rawNanometersPerSecondSquared)
    }
    // endregion
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Area.kt
================================================
package io.github.kevincianfarini.alchemist.type

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.internal.toDecimalString
import io.github.kevincianfarini.alchemist.scalar.mm2
import io.github.kevincianfarini.alchemist.unit.AreaUnit
import io.github.kevincianfarini.alchemist.unit.LengthUnit
import kotlin.jvm.JvmInline
import kotlin.math.pow

/**
 * Represents a measure of area and is capable of storing ±9.22 million kilometers² at millimeter² precision.
 */
@JvmInline
public value class Area internal constructor(internal val rawMillimetersSquared: SaturatingLong) : Comparable<Area> {

    // region SI Arithmetic

    /**
     * Returns the resulting length after dividing this area by the specified [length].
     *
     * This operation attempts to retain precision, but for sufficiently large values of this area some precision
     * may be lost.
     *
     * @throws IllegalArgumentException if both this area and [length] are infinite.
     */
    public operator fun div(length: Length): Length {
        // Try to find the right level which we can perform this operation at without losing precision.
        // --------------------------------------------------------------------------------------------
        // 1 nanometer² / 1 nanometer is 1 nanometer
        // 1 micrometer² / 1 nanometer is 1,000,000 nanometers.
        // 1 millimeter² / 1 nanometer is 1,000,000,000,000 nanometers.
        // --------------------------------------------------------------------------------------------
        val nano2 = rawMillimetersSquared * 1_000_000_000_000
        if (nano2.isFinite()) return Length(nano2 / length.rawNanometers)
        val micro2 = rawMillimetersSquared * 1_000_000
        if (micro2.isFinite()) return Length((micro2 / length.rawNanometers) * 1_000_000)
        return Length((rawMillimetersSquared / length.rawNanometers) * 1_000_000_000_000)
    }

    /**
     * Returns the resulting [Volume] after applying this area over the specified [length].
     *
     * This operation attempts to retain precision, but for sufficiently large values of this area or the
     * specified [length] some precision may be lost.
     *
     * @throws IllegalArgumentException if this area is [infinite][isInfinite] and [length] is zero, or if this area
     * is zero and [length] is infinite.
     */
    public operator fun times(length: Length): Volume {
        return length.toInternationalComponents { giga, mega, kilo, meters, centi, milli, micro, nano ->
            // Try to find the right level which we can perform this operation at without losing precision.
            // --------------------------------------------------------------------------------------------
            // 1 millimeter² * 1 nm is 1 picoliter.
            // 1 millimeter² * 1 μm is 1 nanoliter.
            // 1 millimeter² * 1 mm is 1 microliter.
            // 1 millimeter² * 1 cm is 10 microliters.
            // 1 millimeter² * 1 m is 1 milliliter.
            // 1 millimeter² * 1 km is 1 liter.
            // 1 millimeter² * 1 Mm is 1 kiloliter.
            // 1 millimeter² * 1 Gm is 1 megaliter.
            // --------------------------------------------------------------------------------------------
            val megaliters = rawMillimetersSquared * giga
            val kiloliters = rawMillimetersSquared * mega
            val liters = rawMillimetersSquared * kilo
            val milliliters = rawMillimetersSquared * meters
            val microliters = (rawMillimetersSquared * centi * 10) + (rawMillimetersSquared * milli)
            val nanoliters = rawMillimetersSquared * micro
            val picoliters = rawMillimetersSquared * nano
            // ----------- Try picoliter precision. ------------------------------------------------------
            val picoL = picoliters + (nanoliters * 1_000) + (microliters * 1_000_000) + (milliliters * 1_000_000_000) + (liters * 1_000_000_000_000) + (kiloliters * 1_000_000_000_000_000) + (megaliters * 1_000_000_000_000_000_000)
            if (picoL.isFinite()) return@toInternationalComponents Volume(picoL / 1_000_000_000)
            // ----------- Try nanoliter precision. ------------------------------------------------------
            val nanoL = (picoliters / 1_000) + nanoliters + (microliters * 1_000) + (milliliters * 1_000_000) + (liters * 1_000_000_000) + (kiloliters * 1_000_000_000_000) + (megaliters * 1_000_000_000_000_000)
            if (nanoL.isFinite()) return@toInternationalComponents Volume(nanoL / 1_000_000)
            // ----------- Try microliter precision. -----------------------------------------------------
            val microL = (picoliters / 1_000_000) + (nanoliters / 1_000) + microliters + (milliliters * 1_000) + (liters * 1_000_000) + (kiloliters * 1_000_000_000) + (megaliters * 1_000_000_000_000)
            if (microL.isFinite()) return@toInternationalComponents Volume(microL / 1_000)
            // ----------- Default milliliter precision. -------------------------------------------------
            val milliL = (picoliters / 1_000_000_000) + (nanoliters / 1_000_000) + (microliters / 1_000) + milliliters + (liters * 1_000) + (kiloliters * 1_000_000) + (megaliters * 1_000_000_000)
            Volume(milliL)
        }
    }

    // endregion

    // region Scalar Arithmetic

    /**
     * Returns an area whose value is this area value divided by the specified [scale].
     */
    public operator fun div(scale: Int): Area = div(scale.toLong())

    /**
     * Returns an area whose value is this area value divided by the specified [scale].
     *
     * @throws IllegalArgumentException when [scale] is equal to [Long.MAX_VALUE] or [Long.MIN_VALUE] and this
     * area is [infinite][isInfinite].
     */
    public operator fun div(scale: Long): Area = Area(rawMillimetersSquared / scale)

    /**
     * Returns the number that is the ratio of this and the [other] area value.
     *
     * @throws IllegalArgumentException when both this and the [other] area are [infinite][isInfinite].
     */
    public operator fun div(other: Area): Double {
        return rawMillimetersSquared.toDouble() / other.rawMillimetersSquared.toDouble()
    }

    /**
     * Returns the negative of this area value.
     */
    public operator fun unaryMinus(): Area = Area(-rawMillimetersSquared)

    /**
     * Returns an area whose value is the difference between this and the [other] area value.
     *
     * @throws IllegalArgumentException if this area and the [other] area are both
     * [infinite][isInfinite] but have equivalent signs.
     */
    public operator fun minus(other: Area): Area = Area(rawMillimetersSquared - other.rawMillimetersSquared)

    /**
     * Returns an area whose value is the sum between this and the [other] area value.
     *
     * @throws IllegalArgumentException if this area and the [other] area are both
     * [infinite][isInfinite] but have differing signs.
     */
    public operator fun plus(other: Area): Area = Area(rawMillimetersSquared + other.rawMillimetersSquared)

    /**
     * Returns an area whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this area is [infinite][isInfinite] and [scale] is 0.
     */
    public operator fun times(scale: Int): Area = times(scale.toLong())

    /**
     * Returns an area whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this area is [infinite][isInfinite] and [scale] is 0, or when this
     * area is 0 and scale is [Long.MAX_VALUE] or [Long.MIN_VALUE].
     */
    public operator fun times(scale: Long): Area = Area(rawMillimetersSquared * scale)

    /**
     * Returns an area whose value is multiplied by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this area is [infinite][isInfinite] and [scale] is 0.0 or when this area is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun times(scale: Double): Area = Area(rawMillimetersSquared * scale)

    /**
     * Returns an area whose value is divided by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this area is [infinite][isInfinite] and [scale] is 0.0 or when this area is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun div(scale: Double): Area = Area(rawMillimetersSquared / scale)

    // endregion

    // region Area to Scalar Conversions

    /**
     * Splits this area into megameters², kilometers², meters², centimeters², and millimeters² and executes the [action]
     * with those components. The result of [action] is returned as the result of this function.
     *
     * Infinite areas invoke [action] with [Long.MAX_VALUE] or [Long.MIN_VALUE] for every component, depending on the
     * infinite value's sign.
     */
    public fun <T> toInternationalComponents(
        action: (
            megametersSquared: Long,
            kilometersSquared: Long,
            metersSquared: Long,
            centimetersSquared: Long,
            millimetersSquared: Long,
        ) -> T,
    ): T {
        val mega = rawMillimetersSquared / 1_000_000_000_000_000_000
        val megaRemainder = rawMillimetersSquared % 1_000_000_000_000_000_000
        val kilo = megaRemainder / 1_000_000_000_000
        val kiloRemainder = megaRemainder % 1_000_000_000_000
        val meters = kiloRemainder / 1_000_000
        val metersRemainder = kiloRemainder % 1_000_000
        val centi = metersRemainder / 100
        val milli = metersRemainder % 100
        return action(mega.rawValue, kilo.rawValue, meters.rawValue, centi.rawValue, milli.rawValue)
    }

    /**
     * Returns the value of this area expressed as a [Long] number of the specified [unit]. Infinite values are
     * converted to either [Long.MAX_VALUE] or [Long.MIN_VALUE] depending on its sign.
     */
    public fun toLong(unit: AreaUnit): Long {
        return (rawMillimetersSquared / unit.millimetersSquaredScale).rawValue
    }

    /**
     * Returns the value of this area expressed as a [Long] number of the specified [squareUnit]². Infinite
     * values are converted to either [Long.MAX_VALUE] or [Long.MIN_VALUE] depending on its sign.
     */
    public fun toLong(squareUnit: LengthUnit): Long {
        val nm2 = rawMillimetersSquared * 1_000_000_000_000
        val nm2Scale = squareUnit.nanometerScale.saturated * squareUnit.nanometerScale
        return if (nm2.isFinite() && nm2Scale.isFinite()) {
            nm2 / nm2Scale
        } else {
            val um2 = rawMillimetersSquared * 1_000_000
            val um2Unit = squareUnit.nanometerScale.saturated / 1_000
            val um2Scale = um2Unit * um2Unit
            if (um2.isFinite() && um2Scale.isFinite()) {
                um2 / um2Scale
            } else {
                val mm2Unit = squareUnit.nanometerScale.saturated / 1_000_000
                val mm2Scale = mm2Unit * mm2Unit
                if (rawMillimetersSquared.isFinite() && mm2Scale.isFinite()) {
                    rawMillimetersSquared / mm2Scale
                } else {
                    0L.saturated
                }
            }
        }.rawValue
    }

    /**
     * Returns the value of this area expressed as a [Double] number of the specified [unit]. Infinite values are
     * converted to either [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY] depending on its sign.
     */
    public fun toDouble(unit: AreaUnit): Double {
        return this / unit.millimetersSquaredScale.mm2
    }

    /**
     * Returns the value of this area expressed as a [Double] number of the specific [LengthUnit]². Infinite values are
     * converted to either [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY] depending on its sign.
     */
    public fun toDouble(squareUnit: LengthUnit): Double {
        val nm2 = rawMillimetersSquared.toDouble() * 1_000_000_000_000
        return nm2 / squareUnit.nanometerScale.toDouble().pow(2)
    }

    /**
     * Returns a fractional string representation of this area expressed in the specified [AreaUnit] and is rounded
     * to the specified [decimals].
     */
    public fun toString(unit: AreaUnit, decimals: Int = 0): String = when (isInfinite()) {
        true -> rawMillimetersSquared.toString()
        false -> buildString {
            append(toDouble(unit).toDecimalString(decimals))
            append(unit.symbol)
        }
    }

    /**
     * Returns a fractional string representation of this area expressed in the specified [LengthUnit]² and is rounded
     * to the specified [decimals].
     */
    public fun toString(squareUnit: LengthUnit, decimals: Int = 0): String = when (isInfinite()) {
        true -> rawMillimetersSquared.toString()
        false -> buildString {
            append(toDouble(squareUnit).toDecimalString(decimals))
            append(squareUnit.symbol)
            append("²")
        }
    }

    /**
     * Returns a fractional string representation of this area expressed in the largest [LengthUnit]² quantity which is
     * greater than or equal to 1.
     */
    public override fun toString(): String {
        val largestUnit = LengthUnit.International.entries.asReversed().firstOrNull { squareUnit ->
            toDouble(squareUnit) >= 1.0
        }
        return toString(largestUnit ?: LengthUnit.International.Millimeter, decimals = 2)
    }

    // endregion

    // region Comparisons

    /**
     * Returns true if this area value is infinite.
     */
    public fun isInfinite(): Boolean = rawMillimetersSquared.isInfinite()

    /**
     * Returns true if this area value is finite.
     */
    public fun isFinite(): Boolean = rawMillimetersSquared.isFinite()

    /**
     * Compares this area with the [other] area. Returns zero if this area is equal
     * to the specified [other] area, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    public override fun compareTo(other: Area): Int {
        return rawMillimetersSquared.compareTo(other.rawMillimetersSquared)
    }

    // endregion
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Energy.kt
================================================
package io.github.kevincianfarini.alchemist.type

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.isPreciseToNanosecond
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.internal.throwIllegalArgumentException
import io.github.kevincianfarini.alchemist.internal.toDecimalString
import io.github.kevincianfarini.alchemist.scalar.kilonewtons
import io.github.kevincianfarini.alchemist.scalar.kilowatts
import io.github.kevincianfarini.alchemist.scalar.megawatts
import io.github.kevincianfarini.alchemist.scalar.microwatts
import io.github.kevincianfarini.alchemist.scalar.millijoules
import io.github.kevincianfarini.alchemist.scalar.milliwatts
import io.github.kevincianfarini.alchemist.scalar.watts
import io.github.kevincianfarini.alchemist.unit.EnergyUnit
import kotlin.jvm.JvmInline
import kotlin.time.Duration

/**
 * Represents an amount of energy and is capable of storing ±9.2 petajoules or ±2.56 terawatt-hours at millijoule
 * precision.
 */
@JvmInline
public value class Energy internal constructor(private val rawMillijoules: SaturatingLong) : Comparable<Energy> {

    // region SI Arithmetic

    /**
     * Returns the constant [Force] applied over the specified [length] required to expend this amount of energy.
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this energy or the
     * other [length], some precision may be lost.
     *
     * @throws IllegalArgumentException if both this energy and [length] are infinite.
     */
    public operator fun div(length: Length): Force {
        // TODO This is simplistic and we should attempt to retain precision in the future.
        return ((rawMillijoules / length.rawNanometers) * 1_000).kilonewtons
    }

    /**
     * Returns the constant [Power] applied over the specified [duration] to generate this amount of energy.
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this energy or the
     * other [duration], some precision may be lost.
     *
     * @throws IllegalArgumentException if both this energy and [duration] are infinite.
     */
    public operator fun div(duration: Duration): Power = when {
        rawMillijoules.isInfinite() && duration.isInfinite() -> {
            throwIllegalArgumentException("Dividing two infinite values yields an undefined result.")
        }
        rawMillijoules.isInfinite() -> Power(rawMillijoules)
        duration.isInfinite() -> Power(0L.saturated)
        else -> calculatePower(duration)
    }

    private fun calculatePower(duration: Duration): Power {
        // Try to find the right level which we can perform this operation at without losing precision.
        if (duration.isPreciseToNanosecond()) {
            val power = millijoulesPerNs(rawMillijoules, duration.inWholeNanoseconds)
            if (power.isFinite()) return power
        }
        val ms = duration.inWholeMilliseconds
        val power = millijoulesPerMs(rawMillijoules, ms)
        if (power.isFinite()) return power
        return (rawMillijoules / ms).watts
    }

    private fun millijoulesPerMs(millijoules: SaturatingLong, ms: Long): Power {
        // 1 millijoule per 1 millisecond is 1 watt.
        val watts = (millijoules / ms).watts
        return watts + microjoulesPerMs((millijoules % ms) * 1_000, ms)
    }

    private fun microjoulesPerMs(microjoules: SaturatingLong, ms: Long): Power {
        // 1 microjoule per 1 millisecond is 1 milliwatt.
        val milliwatts = (microjoules / ms).milliwatts
        return milliwatts + nanojoulesPerMs((microjoules % ms) * 1_000, ms)
    }

    private fun nanojoulesPerMs(nanojoules: SaturatingLong, ms: Long): Power {
        // 1 nanojoule per 1 millisecond is 1 microwatt.
        return (nanojoules / ms).microwatts
    }

    private fun millijoulesPerNs(millijoules: SaturatingLong, ns: Long): Power {
        // 1 millijoule per 1 nanosecond is 1 megawatt.
        val megawatts = (millijoules / ns).megawatts
        return megawatts + microjoulesPerNs((millijoules % ns) * 1_000, ns)
    }

    private fun microjoulesPerNs(microjoules: SaturatingLong, ns: Long): Power {
        // 1 microjoule per 1 nanosecond is 1 kilowatt.
        val kilowatts = (microjoules / ns).kilowatts
        return kilowatts + nanojoulesPerNs((microjoules % ns) * 1_000, ns)
    }

    private fun nanojoulesPerNs(nanojoules: SaturatingLong, ns: Long): Power {
        // 1 nanojoule per 1 nanosecond is 1 watt.
        val watts = (nanojoules / ns).watts
        return watts + picojoulesPerNs((nanojoules % ns) * 1_000, ns)
    }

    private fun picojoulesPerNs(picojoules: SaturatingLong, ns: Long): Power {
        // 1 picojoule per 1 nanosecond is 1 milliwatt.
        val milliwatts = (picojoules / ns).milliwatts
        return milliwatts + femtojoulesPerNs((picojoules % ns) * 1_000, ns)
    }

    private fun femtojoulesPerNs(femtojoules: SaturatingLong, ns: Long): Power {
        // 1 femtojoule per 1 nanosecond is 1 microwatt.
        return (femtojoules / ns).microwatts
    }

    // endregion

    // region Scalar Arithmetic

    /**
     * Returns the number that is the ratio of this and the [other] energy value.
     *
     * @throws IllegalArgumentException when both this and the [other] energy are [infinite][isInfinite].
     */
    public operator fun div(other: Energy): Double {
        return rawMillijoules.toDouble() / other.rawMillijoules.toDouble()
    }

    /**
     * Returns an energy whose value is this energy value divided by the specified [scale].
     */
    public operator fun div(scale: Int): Energy {
        return div(scale.toLong())
    }

    /**
     * Returns an energy whose value is this energy value divided by the specified [scale].
     *
     * @throws IllegalArgumentException when [scale] is equal to [Long.MAX_VALUE] or [Long.MIN_VALUE] and this
     * energy is [infinite][isInfinite].
     */
    public operator fun div(scale: Long): Energy {
        return Energy(rawMillijoules / scale)
    }

    /**
     * Returns the negative of this energy value.
     */
    public operator fun unaryMinus(): Energy = Energy(-rawMillijoules)

    /**
     * Returns an energy whose value is the difference between this and the [other] energy value.
     *
     * @throws IllegalArgumentException if this energy and the [other] energy are both
     * [infinite][isInfinite] but have equivalent signs.
     */
    public operator fun minus(other: Energy): Energy {
        return Energy(rawMillijoules - other.rawMillijoules)
    }

    /**
     * Returns an energy whose value is the sum between this and the [other] energy value.
     *
     * @throws IllegalArgumentException if this energy and the [other] energy are both
     * [infinite][isInfinite] but have differing signs.
     */
    public operator fun plus(other: Energy): Energy {
        return Energy(rawMillijoules + other.rawMillijoules)
    }

    /**
     * Returns an energy whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this energy is [infinite][isInfinite] and [scale] is 0.
     */
    public operator fun times(scale: Int): Energy {
        return div(scale.toLong())
    }

    /**
     * Returns an energy whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this energy is [infinite][isInfinite] and [scale] is 0, or when this
     * energy is 0 and scale is [Long.MAX_VALUE] or [Long.MIN_VALUE].
     */
    public operator fun times(scale: Long): Energy {
        return Energy(rawMillijoules * scale)
    }

    /**
     * Returns an energy whose value is multiplied by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this energy is [infinite][isInfinite] and [scale] is 0.0 or when this energy is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun times(scale: Double): Energy = Energy(rawMillijoules * scale)

    /**
     * Returns an energy whose value is divided by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this energy is [infinite][isInfinite] and [scale] is 0.0 or when this energy is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun div(scale: Double): Energy = Energy(rawMillijoules / scale)

    // endregion

    // region Energy to Scalar Conversions

    /**
     * Splits this energy into petajoules, tetrajoules, gigajoules, megajoules, kilojoules, joules, and millijoules
     * and executes the [action] with those components. The result of [action] is returned as the result of this
     * function.
     *
     * Infinite energy values invoke [action] with [Long.MAX_VALUE] or [Long.MIN_VALUE] for every component, depending
     * on the infinite value's sign.
     */
    public fun <T> toInternationalComponents(
        action: (
            petajoules: Long,
            tetrajoules: Long,
            gigajoules: Long,
            megajoules: Long,
            kilojoules: Long,
            joules: Long,
            millijoules: Long,
        ) -> T
    ): T {
        val peta = rawMillijoules / EnergyUnit.International.Petajoule.millijouleScale
        val petaRemainder = rawMillijoules % EnergyUnit.International.Petajoule.millijouleScale
        val tetra = petaRemainder / EnergyUnit.International.Tetrajoule.millijouleScale
        val tetraRemainder = petaRemainder % EnergyUnit.International.Tetrajoule.millijouleScale
        val giga = tetraRemainder / EnergyUnit.International.Gigajoule.millijouleScale
        val gigaRemainder = tetraRemainder % EnergyUnit.International.Gigajoule.millijouleScale
        val mega = gigaRemainder / EnergyUnit.International.Megajoule.millijouleScale
        val megaRemainder = gigaRemainder % EnergyUnit.International.Megajoule.millijouleScale
        val kilo = megaRemainder / EnergyUnit.International.Kilojoule.millijouleScale
        val kiloRemainder = megaRemainder % EnergyUnit.International.Kilojoule.millijouleScale
        val joule = kiloRemainder / EnergyUnit.International.Joule.millijouleScale
        val milliJoule = kiloRemainder % EnergyUnit.International.Joule.millijouleScale
        return action(
            peta.rawValue,
            tetra.rawValue,
            giga.rawValue,
            mega.rawValue,
            kilo.rawValue,
            joule.rawValue,
            milliJoule.rawValue,
        )
    }

    /**
     * Splits this energy into terawatt-hours, gigawatt-hours, megawatt-hours, kilowatt-hours, watt-hours,
     * milliwatt-hours, and microwatt-hours and executes the [action] with those components. The result of [action] is
     * returned as the result of this function.
     *
     * Infinite energy values invoke [action] with [Long.MAX_VALUE], [Long.MIN_VALUE], [Double.POSITIVE_INFINITY], or
     * [Double.NEGATIVE_INFINITY] for every component, depending on the infinite value's sign and the component's type.
     */
    public fun <T> toElectricityComponents(
        action: (
            terawattHours: Long,
            gigawattHours: Long,
            megawattHours: Long,
            kilowattHours: Long,
            wattHours: Long,
            milliwattHours: Long,
            microwattHours: Double,
        ) -> T
    ): T {
        val tera = rawMillijoules / EnergyUnit.Electricity.TerawattHour.millijouleScale
        val teraRemainder = rawMillijoules % EnergyUnit.Electricity.TerawattHour.millijouleScale
        val giga = teraRemainder / EnergyUnit.Electricity.GigawattHour.millijouleScale
        val gigaRemainder = teraRemainder % EnergyUnit.Electricity.GigawattHour.millijouleScale
        val mega = gigaRemainder / EnergyUnit.Electricity.MegawattHour.millijouleScale
        val megaRemainder = gigaRemainder % EnergyUnit.Electricity.MegawattHour.millijouleScale
        val kilo = megaRemainder / EnergyUnit.Electricity.KilowattHour.millijouleScale
        val kiloRemainder = megaRemainder % EnergyUnit.Electricity.KilowattHour.millijouleScale
        val wattHour = kiloRemainder / EnergyUnit.Electricity.WattHour.millijouleScale
        val wattRemainder = kiloRemainder % EnergyUnit.Electricity.WattHour.millijouleScale
        val milliwattHour = wattRemainder / EnergyUnit.Electricity.MilliwattHour.millijouleScale
        val milliwattRemainder = wattRemainder % EnergyUnit.Electricity.MilliwattHour.millijouleScale
        val microwattHours = milliwattRemainder.toDouble() / 3.6 // 3.6 millijoules per microwatt-hour.
        return action(
            tera.rawValue,
            giga.rawValue,
            mega.rawValue,
            kilo.rawValue,
            wattHour.rawValue,
            milliwattHour.rawValue,
            microwattHours,
        )
    }

    /**
     * Returns the value of this energy expressed as a [Long] number of the specified [unit]. Infinite values are
     * converted to either [Long.MAX_VALUE] or [Long.MIN_VALUE] depending on its sign.
     */
    public fun toLong(unit: EnergyUnit): Long {
        return (rawMillijoules / unit.millijouleScale).rawValue
    }

    /**
     * Returns the value of this energy expressed as a [Double] number of the specified [unit]. Infinite values are
     * converted to either [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY] depending on its sign.
     */
    public fun toDouble(unit: EnergyUnit): Double {
        return this / unit.millijouleScale.millijoules
    }

    /**
     * Returns a fractional string representation of this energy expressed in the specified [EnergyUnit] and is rounded
     * to the specified [decimals].
     */
    public fun toString(unit: EnergyUnit, decimals: Int = 0): String = when (rawMillijoules.isInfinite()) {
        true -> rawMillijoules.toString()
        false -> buildString {
            append(toDouble(unit).toDecimalString(decimals))
            append(unit.symbol)
        }
    }

    /**
     * Returns a fractional string representation of this energy expressed in the largest [EnergyUnit.International]
     * quantity which is greater than or equal to 1.
     */
    public override fun toString(): String {
        val largestUnit = EnergyUnit.International.entries.asReversed().firstOrNull { unit ->
            rawMillijoules.absoluteValue / unit.millijouleScale > 0
        }
        return toString(largestUnit ?: EnergyUnit.International.Millijoule, decimals = 2)
    }

    // endregion

    // region Comparisons

    /**
     * Returns true if this area value is infinite.
     */
    public fun isInfinite(): Boolean = rawMillijoules.isInfinite()

    /**
     * Returns true if this area value is finite.
     */
    public fun isFinite(): Boolean = rawMillijoules.isFinite()

    /**
     * Compares this energy with the [other] energy. Returns zero if this energy is equal
     * to the specified [other] energy, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    public override fun compareTo(other: Energy): Int {
        return rawMillijoules.compareTo(other.rawMillijoules)
    }

    // endregion
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Force.kt
================================================
package io.github.kevincianfarini.alchemist.type

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.toDecimalString
import io.github.kevincianfarini.alchemist.scalar.nmPerSecond2
import io.github.kevincianfarini.alchemist.unit.ForceUnit
import kotlin.jvm.JvmInline

/**
 * Represents a measure of force and is capable of storing ±9.2 billion newtons at nanonewton precision.
 */
@JvmInline
public value class Force internal constructor(private val rawNanonewtons: SaturatingLong) : Comparable<Force> {

    // region SI Arithmetic

    /**
     * Returns the resulting [Acceleration] from applying this force to the specified [mass].
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this force or the
     * other [mass], some precision may be lost.
     *
     * @throws IllegalArgumentException if both this force and [mass] are infinite.
     */
    public operator fun div(mass: Mass): Acceleration {
        // 1 nanonewton / 1 microgram is 100 centimeters/second².
        // TODO This is simplistic and we should attempt to retain precision in the future.
        return ((rawNanonewtons / mass.rawMicrograms) * 1_000_000_000).nmPerSecond2
    }

    /**
     * Returns the amount of [Energy] required to apply this force over the specified [length].
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this force or the
     * specified [length], some precision may be lost.
     *
     * @throws IllegalArgumentException when [length] is [infinite][isInfinite] and this force is 0 or vice versa.
     */
    public operator fun times(length: Length): Energy {
        return length.toInternationalComponents { giga, mega, kilo, meters, centi, milli, micro, nano ->
            // Try to find the right level which we can perform this operation at without losing precision.
            // --------------------------------------------------------------------------------------------
            // 1 nN * 1 nm is 1 attojoule.
            // 1 nN * 1 μm is 1 femtojoule.
            // 1 nN * 1 mm is 1 picojoule.
            // 1 nN * 1 cm is 10 picojoules.
            // 1 nN * 1 m is 1 nanojoule.
            // 1 nN * 1 km is 1 microjoule.
            // 1 nN * 1 Mm is 1 millijoule.
            // 1 nN * 1 Gm is 1 joule.
            // --------------------------------------------------------------------------------------------
            val joules = rawNanonewtons * giga
            val millijoules = rawNanonewtons * mega
            val microjoules = rawNanonewtons * kilo
            val nanojoules = rawNanonewtons * meters
            val picojoules = (rawNanonewtons * centi * 10) + (rawNanonewtons * milli)
            val femtojoules = rawNanonewtons * micro
            val attojoules = rawNanonewtons * nano
            // ----------- Try attojoule precision. ------------------------------------------------------
            val attoJ = attojoules + (femtojoules * 1_000) + (picojoules * 1_000_000) + (nanojoules * 1_000_000_000) + (microjoules * 1_000_000_000_000) + (millijoules * 1_000_000_000_000_000) + (joules * 1_000_000_000_000_000_000)
            if (attoJ.isFinite()) return@toInternationalComponents Energy(attoJ / 1_000_000_000_000_000)
            // ----------- Try femtojoule precision. ------------------------------------------------------
            val femtoJ = (attojoules / 1_000) + femtojoules + (picojoules * 1_000) + (nanojoules * 1_000_000) + (microjoules * 1_000_000_000) + (millijoules * 1_000_000_000_000) + (joules * 1_000_000_000_000_000)
            if (femtoJ.isFinite()) return@toInternationalComponents Energy(femtoJ / 1_000_000_000_000)
            // ----------- Try picojoule precision. ------------------------------------------------------
            val picoJ = (attojoules / 1_000_000) + (femtojoules / 1_000) + picojoules + (nanojoules * 1_000) + (microjoules * 1_000_000) + (millijoules * 1_000_000_000) + (joules * 1_000_000_000_000)
            if (picoJ.isFinite()) return@toInternationalComponents Energy(picoJ / 1_000_000_000)
            // ----------- Try nanojoule precision. ------------------------------------------------------
            val nanoJ = (attojoules / 1_000_000_000) + (femtojoules / 1_000_000) + (picojoules / 1_000) + nanojoules + (microjoules * 1_000) + (millijoules * 1_000_000) + (joules * 1_000_000_000)
            if (nanoJ.isFinite()) return@toInternationalComponents Energy(nanoJ / 1_000_000)
            // ----------- Try microjoule precision. ------------------------------------------------------
            val microJ = (attojoules / 1_000_000_000_000) + (femtojoules / 1_000_000_000) + (picojoules / 1_000_000) + (nanojoules / 1_000) + microjoules + (millijoules * 1_000) + (joules * 1_000_000)
            if (microJ.isFinite()) return@toInternationalComponents Energy(microJ / 1_000)
            // ----------- Default millijoule precision. ------------------------------------------------------
            val milliJ = (attojoules / 1_000_000_000_000_000) + (femtojoules / 1_000_000_000_000) + (picojoules / 1_000_000_000) + (nanojoules / 1_000_000) + (microjoules / 1_000) + millijoules + (joules * 1_000)
            Energy(milliJ)
        }
    }

    // endregion

    // region Scalar Arithmetic

    /**
     * Returns the number that is the ratio of this and the [other] force value.
     *
     * @throws IllegalArgumentException when both this and the [other] force are [infinite][isInfinite].
     */
    public operator fun div(other: Force): Double = rawNanonewtons.toDouble() / other.rawNanonewtons.toDouble()

    /**
     * Returns a force whose value is this force value divided by the specified [scale].
     */
    public operator fun div(scale: Int): Force = div(scale.toLong())

    /**
     * Returns a force whose value is this force value divided by the specified [scale].
     *
     * @throws IllegalArgumentException when [scale] is equal to [Long.MAX_VALUE] or [Long.MIN_VALUE] and this
     * force is [infinite][isInfinite].
     */
    public operator fun div(scale: Long): Force = Force(rawNanonewtons / scale)

    /**
     * Returns the negative of this force value.
     */
    public operator fun unaryMinus(): Force = Force(-rawNanonewtons)

    /**
     * Returns a force whose value is the difference between this and the [other] force value.
     *
     * @throws IllegalArgumentException if this force and the [other] force are both
     * [infinite][isInfinite] but have equivalent signs.
     */
    public operator fun minus(other: Force): Force = Force(rawNanonewtons - other.rawNanonewtons)

    /**
     * Returns a force whose value is the sum between this and the [other] force value.
     *
     * @throws IllegalArgumentException if this force and the [other] force are both
     * [infinite][isInfinite] but have differing signs.
     */
    public operator fun plus(other: Force): Force = Force(rawNanonewtons + other.rawNanonewtons)

    /**
     * Returns a force whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this force is [infinite][isInfinite] and [scale] is 0.
     */
    public operator fun times(scale: Int): Force = times(scale.toLong())

    /**
     * Returns a force whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this force is [infinite][isInfinite] and [scale] is 0, or when this
     * force is 0 and scale is [Long.MAX_VALUE] or [Long.MIN_VALUE].
     */
    public operator fun times(scale: Long): Force = Force(rawNanonewtons * scale)

    /**
     * Returns a force whose value is multiplied by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this force is [infinite][isInfinite] and [scale] is 0.0 or when this force is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun times(scale: Double): Force = Force(rawNanonewtons * scale)

    /**
     * Returns a force whose value is divided by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this force is [infinite][isInfinite] and [scale] is 0.0 or when this force is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun div(scale: Double): Force = Force(rawNanonewtons / scale)

    // endregion

    // region Force to Scalar Conversions

    /**
     * Returns the value of this force expressed as a [Long] number of the specified [unit]. Infinite values are
     * converted to either [Long.MAX_VALUE] or [Long.MIN_VALUE] depending on its sign.
     */
    public fun toLong(unit: ForceUnit): Long {
        return (rawNanonewtons / unit.nanonewtonScale).rawValue
    }

    /**
     * Returns the value of this force expressed as a [Double] number of the specified [unit]. Infinite values are
     * converted to either [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY] depending on its sign.
     */
    public fun toDouble(unit: ForceUnit): Double {
        return rawNanonewtons.toDouble() / unit.nanonewtonScale
    }

    /**
     * Returns a fractional string representation of this force expressed in the specified [ForceUnit] and is rounded
     * to the specified [decimals].
     */
    public fun toString(unit: ForceUnit, decimals: Int = 0): String = when {
        isInfinite() -> rawNanonewtons.toString()
        else -> buildString {
            append(toDouble(unit).toDecimalString(decimals))
            append(unit.symbol)
        }
    }

    /**
     * Returns a fractional string representation of this force expressed in the largest [ForceUnit.International]
     * quantity which is greater than or equal to 1.
     */
    public override fun toString(): String {
        val largestUnit = ForceUnit.International.entries.asReversed().firstOrNull { unit ->
            rawNanonewtons / unit.nanonewtonScale > 0
        }
        return toString(largestUnit ?: ForceUnit.International.Nanonewton, decimals = 2)
    }

    // endregion

    // region Comparisons

    /**
     * Returns true if this area value is infinite.
     */
    public fun isInfinite(): Boolean = rawNanonewtons.isInfinite()

    /**
     * Returns true if this area value is finite.
     */
    public fun isFinite(): Boolean = rawNanonewtons.isFinite()

    /**
     * Compares this force with the [other] force. Returns zero if this force is equal
     * to the specified [other] force, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    override fun compareTo(other: Force): Int = rawNanonewtons.compareTo(other.rawNanonewtons)

    // endregion
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Length.kt
================================================
package io.github.kevincianfarini.alchemist.type

import io.github.kevincianfarini.alchemist.internal.POSITIVE_INFINITY
import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.isPreciseToNanosecond
import io.github.kevincianfarini.alchemist.internal.saturated
import io.github.kevincianfarini.alchemist.internal.sign
import io.github.kevincianfarini.alchemist.internal.throwIllegalArgumentException
import io.github.kevincianfarini.alchemist.internal.toDecimalString
import io.github.kevincianfarini.alchemist.scalar.nanometers
import io.github.kevincianfarini.alchemist.scalar.nmPerSecond
import io.github.kevincianfarini.alchemist.unit.LengthUnit
import io.github.kevincianfarini.alchemist.unit.LengthUnit.International.Nanometer
import kotlin.jvm.JvmInline
import kotlin.time.Duration
import kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.Duration.Companion.seconds

/**
 * Represents a measure of length and is capable of storing ±9.2 million kilometers at nanometer precision.
 */
@JvmInline
public value class Length internal constructor(internal val rawNanometers: SaturatingLong) : Comparable<Length> {

    // region SI Arithmetic

    /**
     * Returns the constant [Velocity] required to travel this length in the specified [duration].
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this length or the
     * other [duration], some precision may be lost.
     *
     * @throws IllegalArgumentException if both this length and [duration] are infinite.
     */
    public operator fun div(duration: Duration): Velocity = when {
        rawNanometers.isInfinite() && duration.isInfinite() ->  {
            throwIllegalArgumentException("Dividing two infinite values yields an undefined result.")
        }
        rawNanometers.isInfinite() -> Velocity(rawNanometers * duration.sign)
        duration.isInfinite() -> Velocity(0L.saturated)
        else -> calculateVelocity(duration)
    }

    private fun calculateVelocity(duration: Duration): Velocity {
        // Try to find the right level which we can perform this operation at without losing precision.
        if (duration.isPreciseToNanosecond()) {
            val velocity = nanosPerNs(rawNanometers, duration.inWholeNanoseconds)
            if (velocity.isFinite()) return velocity
        }
        val ms = duration.inWholeMilliseconds
        val velcity = nanosPerMs(rawNanometers, ms)
        if (velcity.isFinite()) return velcity
        return Velocity((rawNanometers / ms) * 1_000)
    }

    private fun nanosPerMs(nanos: SaturatingLong, ms: Long): Velocity {
        // 1 nanometer per 1 millisecond is 1,000 nanometers / second.
        val nanosPerMs = nanos / ms
        val picoRemainder = (nanos % ms) * 1_000
        return (nanosPerMs * 1_000).nmPerSecond + picosPerMs(picoRemainder, ms)
    }

    private fun picosPerMs(picos: SaturatingLong, ms: Long): Velocity {
        // 1 picometer per 1 millisecond is 1 nanometer / second.
        return Velocity(picos / ms)
    }

    private fun nanosPerNs(nanos: SaturatingLong, ns: Long): Velocity {
        // 1 nanometer per 1 nanosecond is 1,000,000,000 nanometers / second.
        val nanosPerNs = nanos / ns
        val picoRemainder = (nanos % ns) * 1_000
        return (nanosPerNs * 1_000_000_000).nmPerSecond + picosPerNs(picoRemainder, ns)
    }

    private fun picosPerNs(picos: SaturatingLong, ns: Long): Velocity {
        // 1 picometer per 1 nanosecond is 1,000,000 nanometers / second.
        val picosPerNs = picos / ns
        val femtoRemainder = (picos % ns) * 1_000
        return (picosPerNs * 1_000_000).nmPerSecond + femtosPerNs(femtoRemainder, ns)
    }

    private fun femtosPerNs(femtos: SaturatingLong, ns: Long): Velocity {
        // 1 femtometer per 1 nanosecond is 1,000 nanometers / second.
        val femtosPerNs = femtos / ns
        val attoRemainder = (femtos % ns) * 1_000
        return (femtosPerNs * 1_000).nmPerSecond + attometersPerNs(attoRemainder, ns)
    }

    private fun attometersPerNs(attos: SaturatingLong, ns: Long): Velocity {
        // 1 attometer per 1 nanosecond is 1 nanometer / second.
        return Velocity(attos / ns)
    }

    /**
     * Returns the [Duration] required to travel this length at the specified constant [velocity].
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this length or the
     * other [velocity], some precision may be lost.
     *
     * @throws IllegalArgumentException if both this length and [velocity] are infinite.
     */
    public operator fun div(velocity: Velocity): Duration = when {
        isInfinite() && velocity.isInfinite() -> {
            throwIllegalArgumentException("Dividing two infinite values yields an undefined result.")
        }
        isInfinite() -> Duration.INFINITE / velocity.rawNanometersPerSecond.sign / rawNanometers.sign
        velocity.isInfinite() -> Duration.ZERO
        else -> calculateDuration(velocity)
    }

    private fun calculateDuration(velocity: Velocity): Duration {
        val duration = seconds(rawNanometers, velocity.rawNanometersPerSecond)
        return if (duration.isFinite()) {
            duration
        } else {
            // Do coarse operation to avoid returning infinity.
            (rawNanometers / velocity.rawNanometersPerSecond).rawValue.seconds
        }
    }

    private fun seconds(nanometers: SaturatingLong, nanometersPerSecond: SaturatingLong): Duration {
        // 1 nanometer divided by 1 nm/s is 1 second.
        val seconds = (nanometers / nanometersPerSecond).rawValue.seconds
        val picometers = (nanometers % nanometersPerSecond) * 1_000
        return seconds + milliseconds(picometers, nanometersPerSecond)
    }

    private fun milliseconds(picometers: SaturatingLong, nanometersPerSecond: SaturatingLong): Duration {
        // 1 picometer divided by 1 nm/s is 1 millisecond.
        val milliseconds = (picometers / nanometersPerSecond).rawValue.milliseconds
        val femtometers = (picometers % nanometersPerSecond) * 1_000
        return milliseconds + microseconds(femtometers, nanometersPerSecond)
    }

    private fun microseconds(femtometers: SaturatingLong, nanometersPerSecond: SaturatingLong): Duration {
        // 1 femtometer divided by 1 nm/s is 1 microsecond.
        val microseconds = (femtometers / nanometersPerSecond).rawValue.microseconds
        val attometers = (femtometers % nanometersPerSecond) * 1_000
        return microseconds + nanoseconds(attometers, nanometersPerSecond)
    }

    private fun nanoseconds(attometers: SaturatingLong, nanometersPerSecond: SaturatingLong): Duration {
        // 1 attometer divided by 1 nm/s is 1 nanosecond.
        return (attometers / nanometersPerSecond).rawValue.nanoseconds
    }

    /**
     * Returns the resulting [Area] after multiplying this length by the [other] length value.
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this length or the
     * [other] length, some precision may be lost.
     *
     * @throws IllegalArgumentException when this length is [infinite][isInfinite] and [other] is 0 or vice versa.
     */
    public operator fun times(other: Length): Area {
        // Omit micrometer and nanometer components for now. The maximum value these components could ever produce is
        // 998,001,998,001 nanometers², and therefore micrometers and nanometers are always lost to precision rounding
        // when converting to millimeters². In the future we may choose more precise measures of Area and this might
        // be revisited.
        return toSaturatedInternationalComponents { giga, mega, kilo, meters, centi, milli, _, _ ->
            other.toSaturatedInternationalComponents { otherGiga, otherMega, otherKilo, otherMeters, otherCenti, otherMilli, _, _ ->
                val gigaSquared = giga * otherGiga
                val megaSquared = mega * otherMega
                val kiloSquared = kilo * otherKilo
                val metersSquared = meters * otherMeters
                val centiSquared = centi * otherCenti
                val millisSquared = milli * otherMilli
                if (gigaSquared != 0L.saturated) {
                    // We can't represent gigameter² at millimeter² precision.
                    Area(POSITIVE_INFINITY * gigaSquared.sign)
                } else {
                    val megaMillis = megaSquared * 1_000_000_000_000_000_000
                    val kiloMillis = kiloSquared * 1_000_000_000_000
                    val meterMillis = metersSquared * 1_000_000
                    val centiMillis = centiSquared * 100
                    Area(megaMillis + kiloMillis + meterMillis + centiMillis + millisSquared)
                }
            }
        }
    }

    /**
     * Returns the resulting [Volume] after applying this length over the specified [area].
     *
     * This operation attempts to retain precision, but for sufficiently large values of this length or the
     * specified [area] some precision may be lost.
     *
     * @throws IllegalArgumentException if this length is [infinite][isInfinite] and [area] is zero, or if this length
     * is zero and [area] is infinite.
     */
    public operator fun times(area: Area): Volume = area * this

    /**
     * Returns the amount of [Energy] required to apply the specified [force] over this length.
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this length or the
     * specified [force], some precision may be lost.
     *
     * @throws IllegalArgumentException when this length is [infinite][isInfinite] and [force] is 0 or vice versa.
     */
    public operator fun times(force: Force): Energy = force * this

    /**
     * Returns an [Area] representing a square with two dimensions of this length.
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this length some
     * precision may be lost.
     */
    public fun squared(): Area = this * this

    /**
     * Returns a [Volume] representing a cube with three dimensions of this length.
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this length some
     * precision may be lost.
     */
    public fun cubed(): Volume = this * this * this

    // endregion

    // region Scalar Arithmetic

    /**
     * Returns the number that is the ratio of this and the [other] length value.
     *
     * @throws IllegalArgumentException when both this and the [other] length are [infinite][isInfinite].
     */
    public operator fun div(other: Length): Double {
        return rawNanometers.toDouble() / other.rawNanometers.toDouble()
    }

    /**
     * Returns a length whose value is this length value divided by the specified [scale].
     */
    public operator fun div(scale: Int): Length {
        return div(scale.toLong())
    }

    /**
     * Returns a length whose value is this length value divided by the specified [scale].
     *
     * @throws IllegalArgumentException when [scale] is equal to [Long.MAX_VALUE] or [Long.MIN_VALUE] and this
     * length is [infinite][isInfinite].
     */
    public operator fun div(scale: Long): Length {
        return Length(rawNanometers / scale)
    }

    /**
     * Returns the negative of this length value.
     */
    public operator fun unaryMinus(): Length = Length(-rawNanometers)

    /**
     * Returns a length whose value is the difference between this and the [other] length value.
     *
     * @throws IllegalArgumentException if this length and the [other] length are both
     * [infinite][isInfinite] but have equivalent signs.
     */
    public operator fun minus(other: Length): Length {
        return Length(rawNanometers - other.rawNanometers)
    }

    /**
     * Returns a length whose value is the sum between this and the [other] length value.
     *
     * @throws IllegalArgumentException if this length and the [other] length are both
     * [infinite][isInfinite] but have differing signs.
     */
    public operator fun plus(other: Length): Length {
        return Length(rawNanometers + other.rawNanometers)
    }

    /**
     * Returns a length whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this length is [infinite][isInfinite] and [scale] is 0.
     */
    public operator fun times(scale: Int): Length {
        return times(scale.toLong())
    }

    /**
     * Returns a length whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this length is [infinite][isInfinite] and [scale] is 0, or when this
     * length is 0 and scale is [Long.MAX_VALUE] or [Long.MIN_VALUE].
     */
    public operator fun times(scale: Long): Length {
        return Length(rawNanometers * scale)
    }

    /**
     * Returns a length whose value is multiplied by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this length is [infinite][isInfinite] and [scale] is 0.0 or when this length is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun times(scale: Double): Length = Length(rawNanometers * scale)

    /**
     * Returns a length whose value is divided by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this length is [infinite][isInfinite] and [scale] is 0.0 or when this length is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun div(scale: Double): Length = Length(rawNanometers / scale)

    // endregion

    // region Length to Scalar Conversions

    private fun <T> toSaturatedInternationalComponents(
        action: (
            gigameters: SaturatingLong,
            megameters: SaturatingLong,
            kilometers: SaturatingLong,
            meters: SaturatingLong,
            centimeters: SaturatingLong,
            millimeters: SaturatingLong,
            micrometers: SaturatingLong,
            nanometers: SaturatingLong,
        ) -> T,
    ): T {
        val giga = rawNanometers / LengthUnit.International.Gigameter.nanometerScale
        val gigaRemainder = rawNanometers % LengthUnit.International.Gigameter.nanometerScale
        val mega = gigaRemainder / LengthUnit.International.Megameter.nanometerScale
        val megaRemainder = gigaRemainder % LengthUnit.International.Megameter.nanometerScale
        val kilo = megaRemainder / LengthUnit.International.Kilometer.nanometerScale
        val kiloRemainder = megaRemainder % LengthUnit.International.Kilometer.nanometerScale
        val meters = kiloRemainder / LengthUnit.International.Meter.nanometerScale
        val metersRemainder = kiloRemainder % LengthUnit.International.Meter.nanometerScale
        val centi = metersRemainder / LengthUnit.International.Centimeter.nanometerScale
        val centiRemainder = metersRemainder % LengthUnit.International.Centimeter.nanometerScale
        val milli = centiRemainder / LengthUnit.International.Millimeter.nanometerScale
        val milliRemainder = centiRemainder % LengthUnit.International.Millimeter.nanometerScale
        val micro = milliRemainder / LengthUnit.International.Micrometer.nanometerScale
        val nano = milliRemainder % LengthUnit.International.Micrometer.nanometerScale
        return action(giga, mega, kilo, meters, centi, milli, micro, nano)
    }

    /**
     * Splits this length into gigameters, megameters, kilometers, meters, centimeters, millimeters, micrometers, and
     * nanometers and executes the [action] with those components. The result of [action] is returned as the result of
     * this function.
     *
     * Infinite length values invoke [action] with [Long.MAX_VALUE] or [Long.MIN_VALUE] for every component, depending
     * on the infinite value's sign.
     */
    public fun <T> toInternationalComponents(
        action: (
            gigameters: Long,
            megameters: Long,
            kilometers: Long,
            meters: Long,
            centimeters: Long,
            millimeters: Long,
            micrometers: Long,
            nanometers: Long,
        ) -> T,
    ): T = toSaturatedInternationalComponents { giga, mega, kilo, meters, centi, milli, micro, nano ->
        action(
            giga.rawValue,
            mega.rawValue,
            kilo.rawValue,
            meters.rawValue,
            centi.rawValue,
            milli.rawValue,
            micro.rawValue,
            nano.rawValue,
        )
    }

    /**
     * Splits this length into miles, yards, feet, and inches and executes the [action] with those components. The
     * result of [action] is returned as the result of this function.
     *
     * Infinite length values invoke [action] with [Long.MAX_VALUE], [Long.MIN_VALUE], [Double.POSITIVE_INFINITY], or
     * [Double.NEGATIVE_INFINITY] for every component, depending on the infinite value's sign and the component's type.
     */
    public fun <T> toUnitedStatesCustomaryComponents(
        action: (miles: Long, yards: Long, feet: Long, inches: Double) -> T,
    ): T {
        val miles = rawNanometers / LengthUnit.UnitedStatesCustomary.Mile.nanometerScale
        val milesRemainder = rawNanometers % LengthUnit.UnitedStatesCustomary.Mile.nanometerScale
        val yards = milesRemainder / LengthUnit.UnitedStatesCustomary.Yard.nanometerScale
        val yardRemainder = milesRemainder % LengthUnit.UnitedStatesCustomary.Yard.nanometerScale
        val feet = yardRemainder / LengthUnit.UnitedStatesCustomary.Foot.nanometerScale
        val feetRemainder = yardRemainder % LengthUnit.UnitedStatesCustomary.Foot.nanometerScale
        val inches = feetRemainder.rawValue.nanometers.toDouble(LengthUnit.UnitedStatesCustomary.Inch)
        return action(miles.rawValue, yards.rawValue, feet.rawValue, inches)
    }

    /**
     * Returns the value of this length expressed as a [Long] number of the specified [unit]. Infinite values are
     * converted to either [Long.MAX_VALUE] or [Long.MIN_VALUE] depending on its sign.
     */
    public fun toLong(unit: LengthUnit): Long {
        return (rawNanometers / unit.nanometerScale).rawValue
    }

    /**
     * Returns the value of this length expressed as a [Double] number of the specified [unit]. Infinite values are
     * converted to either [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY] depending on its sign.
     */
    public fun toDouble(unit: LengthUnit): Double {
        return this / unit.nanometerScale.nanometers
    }

    /**
     * Returns a fractional string representation of this length expressed in the specified [unit] and is rounded
     * to the specified [decimals].
     */
    public fun toString(unit: LengthUnit, decimals: Int = 0): String = when (rawNanometers.isInfinite()) {
        true -> rawNanometers.toString()
        false -> buildString {
            append(toDouble(unit).toDecimalString(decimals))
            append(unit.symbol)
        }
    }

    /**
     * Returns a fractional string representation of this length expressed in the largest [LengthUnit.International]
     * quantity which is greater than or equal to 1.
     */
    public override fun toString(): String {
        val largestUnit = LengthUnit.International.entries.asReversed().firstOrNull { unit ->
            rawNanometers.absoluteValue / unit.nanometerScale > 0
        }
        return toString(largestUnit ?: Nanometer, decimals = 2)
    }

    // endregion

    // region Comparisons

    /**
     * Returns true if this length value is infinite.
     */
    public fun isInfinite(): Boolean = rawNanometers.isInfinite()

    /**
     * Returns true if this length value is finite.
     */
    public fun isFinite(): Boolean = rawNanometers.isFinite()

    /**
     * Compares this length with the [other] length. Returns zero if this length is equal
     * to the specified [other] length, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    public override fun compareTo(other: Length): Int {
        return rawNanometers.compareTo(other.rawNanometers)
    }

    // endregion
}


================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Mass.kt
================================================
package io.github.kevincianfarini.alchemist.type

import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.toDecimalString
import io.github.kevincianfarini.alchemist.unit.MassUnit
import kotlin.jvm.JvmInline
import kotlin.text.Typography.nbsp

/**
 * Represents a measure of mass and is capable of storing ±9.2 billion kilograms at microgram precision.
 */
@JvmInline
public value class Mass internal constructor(internal val rawMicrograms: SaturatingLong) : Comparable<Mass> {

    // region SI Arithmetic

    /**
     * Returns the [Force] required to apply to this mass to achieve the specified [acceleration].
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this mass or the
     * specified [acceleration], some precision may be lost.
     *
     * @throws IllegalArgumentException when [acceleration] is [infinite][isInfinite] and this mass is 0 or vice versa.
     */
    public operator fun times(acceleration: Acceleration): Force = acceleration * this

    // endregion

    // region Scalar Arithmetic

    /**
     * Returns a mass whose value is this mass value divided by the specified [scale].
     */
    public operator fun div(scale: Int): Mass = div(scale.toLong())

    /**
     * Returns a mass whose value is this mass value divided by the specified [scale].
     *
     * @throws IllegalArgumentException when [scale] is equal to [Long.MAX_VALUE] or [Long.MIN_VALUE] and this
     * mass is [infinite][isInfinite].
     */
    public operator fun div(scale: Long): Mass = Mass(rawMicrograms / scale)

    /**
     * Returns the number that is the ratio of this and the [other] mass value.
     *
     * @throws IllegalArgumentException when both this and the [other] mass are [infinite][isInfinite].
     */
    public operator fun div(other: Mass): Double = rawMicrograms.toDouble() / other.rawMicrograms.toDouble()

    /**
     * Returns the negative of this mass value.
     */
    public operator fun unaryMinus(): Mass = Mass(-rawMicrograms)

    /**
     * Returns a mass whose value is the difference between this and the [other] mass value.
     *
     * @throws IllegalArgumentException if this mass and the [other] mass are both
     * [infinite][isInfinite] but have equivalent signs.
     */
    public operator fun minus(other: Mass): Mass = Mass(rawMicrograms - other.rawMicrograms)

    /**
     * Returns a mass whose value is the sum between this and the [other] mass value.
     *
     * @throws IllegalArgumentException if this mass and the [other] mass are both
     * [infinite][isInfinite] but have differing signs.
     */
    public operator fun plus(other: Mass): Mass = Mass(rawMicrograms + other.rawMicrograms)

    /**
     * Returns a mass whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this mass is [infinite][isInfinite] and [scale] is 0.
     */
    public operator fun times(scale: Int): Mass = times(scale.toLong())

    /**
     * Returns a mass whose value is multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this mass is [infinite][isInfinite] and [scale] is 0, or when this
     * mass is 0 and scale is [Long.MAX_VALUE] or [Long.MIN_VALUE].
     */
    public operator fun times(scale: Long): Mass = Mass(rawMicrograms * scale)

    /**
     * Returns a mass whose value is multiplied by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this mass is [infinite][isInfinite] and [scale] is 0.0 or when this mass is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun times(scale: Double): Mass = Mass(rawMicrograms * scale)

    /**
     * Returns a mass whose value is divided by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this mass is [infinite][isInfinite] and [scale] is 0.0 or when this mass is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun div(scale: Double): Mass = Mass(rawMicrograms / scale)

    // endregion

    // region Mass to Scalar Conversions

    /**
     * Splits this mass into teragrams, gigagrams, megagrams, kilograms, grams, milligrams, and micrograms and
     * executes the [action] with those components. The result of [action] is returned as the result of this function.
     *
     * Infinite mass values invoke [action] with [Long.MAX_VALUE] or [Long.MIN_VALUE] for every component, depending
     * on the infinite value's sign.
     */
    public fun <T> toInternationalComponents(
        action: (
            teragrams: Long,
            gigagrams: Long,
            megagrams: Long,
            kilograms: Long,
            grams: Long,
            milligrams: Long,
            micrograms: Long,
        ) -> T
    ): T {
        val tera = rawMicrograms / MassUnit.International.Teragram.microgramScale
        val teraRemainder = rawMicrograms % MassUnit.International.Teragram.microgramScale
        val giga = teraRemainder / MassUnit.International.Gigagram.microgramScale
        val gigaRemainder = teraRemainder % MassUnit.International.Gigagram.microgramScale
        val mega = gigaRemainder / MassUnit.International.Megagram.microgramScale
        val megaRemainder = gigaRemainder % MassUnit.International.Megagram.microgramScale
        val kilo = megaRemainder / MassUnit.International.Kilogram.microgramScale
        val kiloRemainder = megaRemainder % MassUnit.International.Kilogram.microgramScale
        val grams = kiloRemainder / MassUnit.International.Gram.microgramScale
        val gramRemainder = kiloRemainder % MassUnit.International.Gram.microgramScale
        val milli = gramRemainder / MassUnit.International.Milligram.microgramScale
        val micro = gramRemainder % MassUnit.International.Megagram.microgramScale
        return action(
            tera.rawValue,
            giga.rawValue,
            mega.rawValue,
            kilo.rawValue,
            grams.rawValue,
            milli.rawValue,
            micro.rawValue
        )
    }

    /**
     * Returns the value of this mass expressed as a [Long] number of the specified [unit]. Infinite values are
     * converted to either [Long.MAX_VALUE] or [Long.MIN_VALUE] depending on its sign.
     */
    public fun toLong(unit: MassUnit): Long {
        return (rawMicrograms / unit.microgramScale).rawValue
    }

    /**
     * Returns the value of this mass expressed as a [Double] number of the specified [unit]. Infinite values are
     * converted to either [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY] depending on its sign.
     */
    public fun toDouble(unit: MassUnit): Double {
        return rawMicrograms.toDouble() / unit.microgramScale.toDouble()
    }

    /**
     * Returns a fractional string representation of this mass expressed in the specified [unit] and is rounded
     * to the specified [decimals].
     */
    public fun toString(unit: MassUnit, decimals: Int = 0): String = when (isInfinite()) {
        true -> rawMicrograms.toString()
        false -> buildString {
            append(toDouble(unit).toDecimalString(decimals))
            append(nbsp)
            append(unit.symbol)
        }
    }

    /**
     * Returns a fractional string representation of this mass expressed in the largest [MassUnit.International]
     * quantity which is greater than or equal to 1.
     */
    override fun toString(): String {
        val largestUnit = MassUnit.International.entries.asReversed().firstOrNull { unit ->
            rawMicrograms.absoluteValue / unit.microgramScale > 0
        }
        return toString(largestUnit ?: MassUnit.International.Microgram, decimals = 2)
    }

    // endregion

    // region Comparisons

    /**
     * Returns true if this area value is infinite.
     */
    public fun isInfinite(): Boolean = rawMicrograms.isInfinite()

    /**
     * Returns true if this area value is finite.
     */
    public fun isFinite(): Boolean = rawMicrograms.isFinite()

    /**
     * Compares this mass with the [other] mass. Returns zero if this mass is equal
     * to the specified [other] mass, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    override fun compareTo(other: Mass): Int = rawMicrograms.compareTo(other.rawMicrograms)

    // endregion
}



================================================
FILE: src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Power.kt
================================================
package io.github.kevincianfarini.alchemist.type

import io.github.kevincianfarini.alchemist.internal.POSITIVE_INFINITY
import io.github.kevincianfarini.alchemist.internal.SaturatingLong
import io.github.kevincianfarini.alchemist.internal.sign
import io.github.kevincianfarini.alchemist.internal.toDecimalComponents
import io.github.kevincianfarini.alchemist.internal.toDecimalString
import io.github.kevincianfarini.alchemist.scalar.microwatts
import io.github.kevincianfarini.alchemist.unit.PowerUnit
import kotlin.jvm.JvmInline
import kotlin.time.Duration

/**
 * Represents an amount of power and is capable of storing ±9.22 terawatts at microwatt precision.
 */
@JvmInline
public value class Power internal constructor(private val rawMicrowatts: SaturatingLong) : Comparable<Power> {

    // region SI Arithmetic

    /**
     * Returns the resulting [Energy] from applying this power over the specified [duration].
     *
     * This operation attempts to retain precision, but for sufficiently large values of either this power or [duration],
     * some precision may be lost.
     *
     * @throws IllegalArgumentException if this power is infinite and duration is zero, or if this power is zero and
     * duration is infinite.
     */
    public operator fun times(duration: Duration): Energy {
        return when {
            duration.isInfinite() || rawMicrowatts.isInfinite() -> {
                Energy(POSITIVE_INFINITY * duration.sign * rawMicrowatts)
            }
            else -> duration.toDecimalComponents { kiloseconds, seconds, millis, micros, nanos ->
                // Try to find the right level which we can perform this operation at without losing precision.
                // --------------------------------------------------------------------------------------------
                // 1 microwatt * 1 nanosecond is 1 femtojoule.
                // 1 microwatt * 1 microsecond is 1 picojoule.
                // 1 microwatt * 1 millisecond is 1 nanojoule.
                // 1 microwatt * 1 second is 1 microjoule.
                // 1 microwatt * 1,000 seconds is 1 millijoule.
                // --------------------------------------------------------------------------------------------
                val millijoules = rawMicrowatts * kiloseconds
                val microjoules = rawMicrowatts * seconds
                val nanojoules = rawMicrowatts * millis
                val picojoules = rawMicrowatts * micros
                val femtojoules = rawMicrowatts * nanos
                // ----------- Try femtojoule precision. ------------------------------------------------------
                val femtoJ = femtojoules + (picojoules * 1_000) + (nanojoules * 1_000_000) + (microjoules * 1_000_000_000) + (millijoules * 1_000_000_000_000)
                if (femtoJ.isFinite()) return@toDecimalComponents Energy(femtoJ / 1_000_000_000_000)
                // ----------- Try picojoule precision. -------------------------------------------------------
                val picoJ = (femtojoules / 1_000) + picojoules + (nanojoules * 1_000) + (microjoules * 1_000_000) + (millijoules * 1_000_000_000)
                if (picoJ.isFinite()) return@toDecimalComponents Energy(picoJ / 1_000_000_000)
                // ----------- Try nanojoule precision. -------------------------------------------------------
                val nanoJ = (femtojoules / 1_000_000) + (picojoules / 1_000) + nanojoules + (microjoules * 1_000) + (millijoules * 1_000_000)
                if (nanoJ.isFinite()) return@toDecimalComponents Energy(nanoJ / 1_000_000)
                // ----------- Try microjoule precision. -------------------------------------------------------
                val microJ = (femtojoules / 1_000_000_000) + (picojoules / 1_000_000) + (nanojoules / 1_000) + microjoules + (millijoules * 1_000)
                if (microJ.isFinite()) return@toDecimalComponents Energy(microJ / 1_000)
                // ----------- Default microjoule precision. ---------------------------------------------------
                val milliJ = (femtojoules / 1_000_000_000_000) + (picojoules / 1_000_000_000) + (nanojoules / 1_000_000) + (microjoules / 1_000) + millijoules
                Energy(milliJ)
            }
        }
    }

    // endregion

    // region Scalar Arithmetic

    /**
     * Returns the number that is the ratio of this and the [other] power value.
     *
     * @throws IllegalArgumentException when both this and the [other] power are [infinite][isInfinite].
     */
    public operator fun div(other: Power): Double {
        return rawMicrowatts.toDouble() / other.rawMicrowatts.toDouble()
    }

    /**
     * Returns a power whose value is this power value divided by the specified [scale].
     */
    public operator fun div(scale: Int): Power = div(scale.toLong())

    /**
     * Returns a power whose value is this power value divided by the specified [scale].
     *
     * @throws IllegalArgumentException when [scale] is equal to [Long.MAX_VALUE] or [Long.MIN_VALUE] and this
     * power is [infinite][isInfinite].
     */
    public operator fun div(scale: Long): Power = Power(rawMicrowatts / scale)

    /**
     * Returns the negative of this power value.
     */
    public operator fun unaryMinus(): Power = Power(-rawMicrowatts)

    /**
     * Returns a power whose value is the difference between this and the [other] power value.
     *
     * @throws IllegalArgumentException if this power and the [other] power are both
     * [infinite][isInfinite] but have equivalent signs.
     */
    public operator fun minus(other: Power): Power = Power(rawMicrowatts - other.rawMicrowatts)

    /**
     * Returns a power whose value is the sum between this and the [other] power value.
     *
     * @throws IllegalArgumentException if this power and the [other] power are both
     * [infinite][isInfinite] but have differing signs.
     */
    public operator fun plus(other: Power): Power = Power(rawMicrowatts + other.rawMicrowatts)

    /**
     * Returns a power whose value is this power multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this power is [infinite][isInfinite] and [scale] is 0.
     */
    public operator fun times(scale: Int): Power = times(scale.toLong())

    /**
     * Returns a power whose value is this power multiplied by the specified [scale].
     *
     * @throws IllegalArgumentException when this power is [infinite][isInfinite] and [scale] is 0, or when this
     * power is 0 and scale is [Long.MAX_VALUE] or [Long.MIN_VALUE].
     */
    public operator fun times(scale: Long): Power = Power(rawMicrowatts * scale)

    /**
     * Returns a power whose value is multiplied by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this power is [infinite][isInfinite] and [scale] is 0.0 or when this power is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun times(scale: Double): Power = Power(rawMicrowatts * scale)

    /**
     * Returns a power whose value is divided by the specified [scale]. This operation may be rounded when the result
     * cannot be precisely represented with a [Double] number.
     *
     * @throws IllegalArgumentException when this power is [infinite][isInfinite] and [scale] is 0.0 or when this power is 0
     * and scale is [infinite][Double.isInfinite].
     */
    public operator fun div(scale: Double): Power = Power(rawMicrowatts / scale)

    // endregion

    // region Power to Scalar Conversions

    /**
     * Returns the value of this power expressed as a [Long] number of the specified [unit]. Infinite values are
     * converted to either [Long.MAX_VALUE] or [Long.MIN_VALUE] depending on its sign.
     */
    public fun toLong(unit: PowerUnit): Long {
        return (rawMicrowatts / unit.microwattScale).rawValue
    }

    /**
     * Returns the value of this power expressed as a [Double] number of the specified [unit]. Infinite values are
     * converted to either [Double.POSITIVE_INFINITY] or [Double.NEGATIVE_INFINITY] depending on its sign.
     */
    public fun toDouble(unit: PowerUnit): Double {
        return this / unit.microwattScale.microwatts
    }

    /**
     * Returns a fractional string representation of this power expressed in the specified [unit] and is rounded
     * to the specified [decimals].
     */
    public fun toString(unit: PowerUnit, decimals: Int = 0): String = when (rawMicrowatts.isInfinite()) {
        true -> rawMicrowatts.toString()
        false -> buildString {
            append(toDouble(unit).toDecimalString(decimals))
            append(unit.symbol)
        }
    }

    /**
     * Returns a fractional string representation of this power expressed in the largest [PowerUnit.International]
     * quantity which is greater than or equal to 1.
     */
    override fun toString(): String {
        val largestUnit = PowerUnit.International.entries.asReversed().firstOrNull { unit ->
            rawMicrowatts.absoluteValue / unit.microwattScale > 0
        }
        return toString(largestUnit ?: PowerUnit.International.Microwatt, decimals = 2)
    }

    // endregion

    // region Comparisons

    /**
     * Returns true if this area value is infinite.
     */
    public fun isInfinite(): Boolean = rawMicrowatts.isInfinite()

    /**
     * Returns true if this area value is finite.
     */
    public fun isFinite(): Boolean = rawMicrowatts.isFinite()

    /**
     * Compares this power with the [other] power. Returns zero if this power is equal
     * to the specified [other] power, a negative numbe
Download .txt
gitextract_1pez5g1s/

├── .github/
│   ├── .java-version
│   ├── renovate.json5
│   └── workflows/
│       ├── gradle-wrapper.yaml
│       ├── release.yaml
│       └── test.yaml
├── .gitignore
├── LICENSE.txt
├── ModuleDocumentation.md
├── README.md
├── build.gradle.kts
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src/
    ├── commonMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       ├── internal/
    │                       │   ├── SaturatingLong.kt
    │                       │   ├── double.kt
    │                       │   ├── duration.kt
    │                       │   └── exception.kt
    │                       ├── scalar/
    │                       │   ├── acceleration.kt
    │                       │   ├── area.kt
    │                       │   ├── energy.kt
    │                       │   ├── force.kt
    │                       │   ├── length.kt
    │                       │   ├── mass.kt
    │                       │   ├── power.kt
    │                       │   ├── temperature.kt
    │                       │   ├── velocity.kt
    │                       │   └── volume.kt
    │                       ├── type/
    │                       │   ├── Acceleration.kt
    │                       │   ├── Area.kt
    │                       │   ├── Energy.kt
    │                       │   ├── Force.kt
    │                       │   ├── Length.kt
    │                       │   ├── Mass.kt
    │                       │   ├── Power.kt
    │                       │   ├── Temperature.kt
    │                       │   ├── Velocity.kt
    │                       │   ├── Volume.kt
    │                       │   └── duration.kt
    │                       └── unit/
    │                           ├── AreaUnit.kt
    │                           ├── EnergyUnit.kt
    │                           ├── ForceUnit.kt
    │                           ├── LengthUnit.kt
    │                           ├── MassUnit.kt
    │                           ├── PowerUnit.kt
    │                           ├── TemperatureUnit.kt
    │                           └── VolumeUnit.kt
    ├── commonTest/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       ├── Long.kt
    │                       ├── ignore.kt
    │                       ├── internal/
    │                       │   ├── DoubleTest.kt
    │                       │   └── SaturatingLongTest.kt
    │                       └── type/
    │                           ├── AccelerationTest.kt
    │                           ├── AreaTest.kt
    │                           ├── EnergyTest.kt
    │                           ├── ForceTest.kt
    │                           ├── LengthTest.kt
    │                           ├── PowerTest.kt
    │                           ├── TemperatureTest.kt
    │                           ├── VelocityTest.kt
    │                           └── VolumeTest.kt
    ├── jsMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.js.kt
    ├── jsTest/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── ignore.js.kt
    ├── jvmMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.jvm.kt
    ├── nativeMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.native.kt
    ├── wasmJsMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.wasmjs.kt
    ├── wasmJsTest/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── ignore.wasmjs.kt
    ├── wasmWasiMain/
    │   └── kotlin/
    │       └── io/
    │           └── github/
    │               └── kevincianfarini/
    │                   └── alchemist/
    │                       └── internal/
    │                           └── double.wasmwasi.kt
    └── wasmWasiTest/
        └── kotlin/
            └── io/
                └── github/
                    └── kevincianfarini/
                        └── alchemist/
                            └── ignore.wasmwasi.kt
Condensed preview — 71 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (313K chars).
[
  {
    "path": ".github/.java-version",
    "chars": 3,
    "preview": "24\n"
  },
  {
    "path": ".github/renovate.json5",
    "chars": 691,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n  extends: [\n    'config:recommended',\n  ],\n  reviewer"
  },
  {
    "path": ".github/workflows/gradle-wrapper.yaml",
    "chars": 261,
    "preview": "name: gradle-wrapper\n\non:\n  pull_request:\n    paths:\n      - 'gradlew'\n      - 'gradlew.bat'\n      - 'gradle/wrapper/**'"
  },
  {
    "path": ".github/workflows/release.yaml",
    "chars": 1079,
    "preview": "name: release\n\non:\n  push:\n    branches:\n      - 'trunk'\n\nenv:\n  GRADLE_OPTS: \"-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle."
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 1153,
    "preview": "name: test\n\non:\n  pull_request: {}\n  push:\n    branches:\n      - 'trunk'\n\nenv:\n  GRADLE_OPTS: \"-Dorg.gradle.jvmargs=-Xmx"
  },
  {
    "path": ".gitignore",
    "chars": 47,
    "preview": ".idea\n.gradle\n**/build\nlocal.properties\n.kotlin"
  },
  {
    "path": "LICENSE.txt",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "ModuleDocumentation.md",
    "chars": 4783,
    "preview": "# Module alchemist\n\nAlchemist allows you to manage physical quanities defined in the [International System of Units](htt"
  },
  {
    "path": "README.md",
    "chars": 5320,
    "preview": "# Alchemist\n\nManage physical units. Inspired by kotlin.time.Duration. Alchemist allow type safe arithmetic between diffe"
  },
  {
    "path": "build.gradle.kts",
    "chars": 2468,
    "preview": "import com.vanniktech.maven.publish.SonatypeHost\nimport org.jetbrains.kotlin.gradle.ExperimentalWasmDsl\nimport org.jetbr"
  },
  {
    "path": "gradle/libs.versions.toml",
    "chars": 503,
    "preview": "[versions]\ndokka = \"2.0.0\"\nkmpmt = \"0.1.1\"\nkotlin = \"2.1.21\"\npublish = \"0.30.0\"\n\n[libraries]\nkotlin-test-core = { module"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 253,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
  },
  {
    "path": "gradle.properties",
    "chars": 954,
    "preview": "kotlin.code.style=official\n\nGROUP=io.github.kevincianfarini.alchemist\nPOM_ARTIFACT_ID=alchemist\nVERSION_NAME=0.2.1-SNAPS"
  },
  {
    "path": "gradlew",
    "chars": 8710,
    "preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "gradlew.bat",
    "chars": 2937,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "settings.gradle.kts",
    "chars": 185,
    "preview": "rootProject.name = \"alchemist\"\n\npluginManagement {\n    repositories {\n        mavenCentral()\n    }\n}\n\ndependencyResoluti"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/internal/SaturatingLong.kt",
    "chars": 6676,
    "preview": "@file:Suppress(\"NOTHING_TO_INLINE\")\n\npackage io.github.kevincianfarini.alchemist.internal\n\nimport kotlin.jvm.JvmInline\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/internal/double.kt",
    "chars": 216,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\n/**\n * Returns a decimal string that has been rounded to the speci"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/internal/duration.kt",
    "chars": 2273,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\nimport io.github.kevincianfarini.alchemist.type.Energy\nimport kotl"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/internal/exception.kt",
    "chars": 479,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\n/**\n * Ensures that we don't inline the exception throwing instruc"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/acceleration.kt",
    "chars": 476,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/area.kt",
    "chars": 4699,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/energy.kt",
    "chars": 9190,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/force.kt",
    "chars": 5281,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/length.kt",
    "chars": 8352,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/mass.kt",
    "chars": 5490,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.saturated\nimport"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/power.kt",
    "chars": 5557,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/temperature.kt",
    "chars": 6982,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/velocity.kt",
    "chars": 456,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\ni"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/scalar/volume.kt",
    "chars": 4987,
    "preview": "package io.github.kevincianfarini.alchemist.scalar\n\nimport io.github.kevincianfarini.alchemist.internal.saturated\nimport"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Acceleration.kt",
    "chars": 12331,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Area.kt",
    "chars": 14491,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Energy.kt",
    "chars": 15465,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Force.kt",
    "chars": 10994,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Length.kt",
    "chars": 20661,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.POSITIVE_INFINITY\n"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Mass.kt",
    "chars": 8644,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Power.kt",
    "chars": 9982,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.POSITIVE_INFINITY\n"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Temperature.kt",
    "chars": 6507,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Velocity.kt",
    "chars": 11565,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/Volume.kt",
    "chars": 9099,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/type/duration.kt",
    "chars": 1565,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport kotlin.time.Duration\n\n\n/**\n * Returns the resulting [Velocity] "
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/unit/AreaUnit.kt",
    "chars": 861,
    "preview": "package io.github.kevincianfarini.alchemist.unit\n\n\n/**\n * A unit of area precise to the millimeter².\n */\npublic interfac"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/unit/EnergyUnit.kt",
    "chars": 1452,
    "preview": "package io.github.kevincianfarini.alchemist.unit\n\n/**\n * A unit of energy precise to the millijoule.\n */\npublic interfac"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/unit/ForceUnit.kt",
    "chars": 908,
    "preview": "package io.github.kevincianfarini.alchemist.unit\n\n/**\n * A unit of force precise to the nanonewton.\n */\npublic interface"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/unit/LengthUnit.kt",
    "chars": 1352,
    "preview": "package io.github.kevincianfarini.alchemist.unit\n\n/**\n * A unit of length precise to the nanometer.\n */\npublic interface"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/unit/MassUnit.kt",
    "chars": 884,
    "preview": "package io.github.kevincianfarini.alchemist.unit\n\n/**\n * A unit of mass precise to the microgram.\n */\npublic interface M"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/unit/PowerUnit.kt",
    "chars": 913,
    "preview": "package io.github.kevincianfarini.alchemist.unit\n\n/**\n * A unit of power precise to the microwatt.\n */\npublic interface "
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/unit/TemperatureUnit.kt",
    "chars": 7297,
    "preview": "package io.github.kevincianfarini.alchemist.unit\n\nimport io.github.kevincianfarini.alchemist.internal.SaturatingLong\nimp"
  },
  {
    "path": "src/commonMain/kotlin/io/github/kevincianfarini/alchemist/unit/VolumeUnit.kt",
    "chars": 1018,
    "preview": "package io.github.kevincianfarini.alchemist.unit\n\nimport kotlin.text.Typography.nbsp\n\n/**\n * A unit of volume precise to"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/Long.kt",
    "chars": 713,
    "preview": "package io.github.kevincianfarini.alchemist\n\n/**\n * Convert this [Long] to its binary string representation. The result "
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/ignore.kt",
    "chars": 416,
    "preview": "@file:OptIn(ExperimentalMultiplatform::class)\n\npackage io.github.kevincianfarini.alchemist\n\n/**\n * Ignores a test for th"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/internal/DoubleTest.kt",
    "chars": 995,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\nimport io.github.kevincianfarini.alchemist.WasmWasiIgnore\nimport k"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/internal/SaturatingLongTest.kt",
    "chars": 11831,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\nimport io.github.kevincianfarini.alchemist.WasmJsIgnore\nimport io."
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/AccelerationTest.kt",
    "chars": 1667,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.WasmWasiIgnore\nimport io.gi"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/AreaTest.kt",
    "chars": 9670,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.WasmWasiIgnore\nimport io.gi"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/EnergyTest.kt",
    "chars": 8384,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.NEGATIVE_INFINITY\n"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/ForceTest.kt",
    "chars": 514,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.scalar.millijoules\nimport i"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/LengthTest.kt",
    "chars": 9923,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.WasmWasiIgnore\nimport io.gi"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/PowerTest.kt",
    "chars": 4072,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.internal.NEGATIVE_INFINITY\n"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/TemperatureTest.kt",
    "chars": 2301,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.WasmWasiIgnore\nimport io.gi"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/VelocityTest.kt",
    "chars": 2122,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.WasmWasiIgnore\nimport io.gi"
  },
  {
    "path": "src/commonTest/kotlin/io/github/kevincianfarini/alchemist/type/VolumeTest.kt",
    "chars": 1125,
    "preview": "package io.github.kevincianfarini.alchemist.type\n\nimport io.github.kevincianfarini.alchemist.WasmWasiIgnore\nimport io.gi"
  },
  {
    "path": "src/jsMain/kotlin/io/github/kevincianfarini/alchemist/internal/double.js.kt",
    "chars": 1123,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\nimport kotlin.math.abs\nimport kotlin.math.pow\nimport kotlin.math.r"
  },
  {
    "path": "src/jsTest/kotlin/io/github/kevincianfarini/alchemist/ignore.js.kt",
    "chars": 92,
    "preview": "package io.github.kevincianfarini.alchemist\n\nactual typealias JsIgnore = kotlin.test.Ignore\n"
  },
  {
    "path": "src/jvmMain/kotlin/io/github/kevincianfarini/alchemist/internal/double.jvm.kt",
    "chars": 438,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\nimport java.math.RoundingMode\nimport java.text.DecimalFormat\n\ninte"
  },
  {
    "path": "src/nativeMain/kotlin/io/github/kevincianfarini/alchemist/internal/double.native.kt",
    "chars": 523,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\nimport kotlinx.cinterop.ByteVar\nimport kotlinx.cinterop.Experiment"
  },
  {
    "path": "src/wasmJsMain/kotlin/io/github/kevincianfarini/alchemist/internal/double.wasmjs.kt",
    "chars": 1125,
    "preview": "package io.github.kevincianfarini.alchemist.internal\n\nimport kotlin.math.abs\nimport kotlin.math.pow\nimport kotlin.math.r"
  },
  {
    "path": "src/wasmJsTest/kotlin/io/github/kevincianfarini/alchemist/ignore.wasmjs.kt",
    "chars": 95,
    "preview": "package io.github.kevincianfarini.alchemist\n\nactual typealias WasmJsIgnore = kotlin.test.Ignore"
  },
  {
    "path": "src/wasmWasiMain/kotlin/io/github/kevincianfarini/alchemist/internal/double.wasmwasi.kt",
    "chars": 2761,
    "preview": "@file:Suppress(\n    \"LocalVariableName\",\n    \"JoinDeclarationAndAssignment\",\n    \"CanBeVal\",\n    \"FunctionName\",\n    \"Li"
  },
  {
    "path": "src/wasmWasiTest/kotlin/io/github/kevincianfarini/alchemist/ignore.wasmwasi.kt",
    "chars": 98,
    "preview": "package io.github.kevincianfarini.alchemist\n\nactual typealias WasmWasiIgnore = kotlin.test.Ignore\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the kevincianfarini/alchemist GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 71 files (290.8 KB), approximately 76.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!