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