Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
name: CI

"on":
pull_request:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: "1"

jobs:
quality:
name: quality
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install stable Rust
uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # stable
with:
toolchain: stable
components: clippy,rustfmt

- name: Check formatting
run: cargo fmt --check

- name: Lint the stable library and unit tests
run: cargo clippy --lib --tests -- -D warnings

- name: Check public documentation
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps

stable-test:
name: stable-test (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install stable Rust
uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # stable
with:
toolchain: stable

- name: Test the stable library
run: cargo test --lib

- name: Test public documentation
run: cargo test --doc

- name: Check release library compilation
run: cargo check --release --lib

msrv:
name: msrv (1.85.0)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install MSRV
uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # 1.85.0
with:
toolchain: 1.85.0

- name: Check package and tests on MSRV
run: |
cargo check --lib
cargo test --lib
cargo test --doc

no-std-targets:
name: no-std (${{ matrix.target }})
strategy:
fail-fast: false
matrix:
target:
- aarch64-unknown-none
- riscv64gc-unknown-none-elf
- wasm32-unknown-unknown
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install stable Rust and portable target
uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # stable
with:
toolchain: stable
targets: ${{ matrix.target }}

- name: Compile the default no_std API
run: cargo check --no-default-features --target ${{ matrix.target }}

nightly-features:
name: nightly-features
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install nightly Rust
uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # nightly
with:
toolchain: nightly

- name: Test every opt-in nightly path
run: cargo test --all-targets --all-features

- name: Check nightly public documentation
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --all-features --no-deps

publish-dry-run:
name: publish-dry-run
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install stable Rust
uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # stable
with:
toolchain: stable

- name: Verify the extracted publication archive
run: bash ci/publish-dry-run.sh
59 changes: 59 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Publish crate

"on":
release:
types:
- published

permissions:
contents: read

concurrency:
group: crates-io-release
cancel-in-progress: false

env:
CARGO_TERM_COLOR: always

jobs:
publish:
name: publish-crates-io
runs-on: ubuntu-latest
timeout-minutes: 30
# Trusted Publishing grants this job only the ability to read the released
# source and to mint the short-lived OIDC identity that crates.io checks.
permissions:
contents: read
id-token: write
steps:
# Pin checkout to the SHA captured by the release event. The tag name is
# mutable, so it is checked separately rather than used as the source.
- name: Check out the released commit
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}

- name: Install stable Rust
uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # stable
with:
toolchain: stable

- name: Require a matching tag on the default branch
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RELEASE_SHA: ${{ github.sha }}
run: bash ci/check-release.sh "${{ github.event.release.tag_name }}"

- name: Verify the extracted publication archive
run: bash ci/publish-dry-run.sh

- name: Authenticate with crates.io through Trusted Publishing
id: crates_io_auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5

- name: Publish the verified archive
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates_io_auth.outputs.token }}
run: cargo publish --locked
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
.DS_Store
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "unsigned-float"
version = "0.2.2"
edition = "2024"
rust-version = "1.85"
description = "Unsigned floating-point formats for non-negative numeric domains."
license = "MIT"
repository = "https://github.com/MicroPerceptron/ufloat"
Expand All @@ -14,7 +15,8 @@ default = []
soft-float = []
f16 = []
f128 = []
nightly = ["f16", "f128"]
simd = []
nightly = ["f16", "f128", "simd"]

[dependencies]
libm = "0.2"
Expand Down
61 changes: 59 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[![Crates.io](https://img.shields.io/crates/v/unsigned-float.svg)](https://crates.io/crates/unsigned-float)
[![docs.rs](https://docs.rs/unsigned-float/badge.svg)](https://docs.rs/unsigned-float)
[![CI](https://github.com/MicroPerceptron/ufloat/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/MicroPerceptron/ufloat/actions/workflows/ci.yml)
[![GitHub last commit](https://img.shields.io/github/last-commit/MicroPerceptron/ufloat.svg)](https://github.com/MicroPerceptron/ufloat/commits/main)
[![GitHub license](https://img.shields.io/github/license/MicroPerceptron/ufloat.svg)](https://github.com/MicroPerceptron/ufloat/blob/main/LICENSE)
[![GitHub issues](https://img.shields.io/github/issues/MicroPerceptron/ufloat.svg)](https://github.com/MicroPerceptron/ufloat/issues)
Expand Down Expand Up @@ -186,7 +187,8 @@ output.
| ------------ | ------- | ----------------------------------------------------------------------------------------------------------- |
| `f16` | No | Uses nightly primitive `f16` for `Uf8` arithmetic dispatch. Requires nightly Rust. |
| `f128` | No | Enables `Uf64`/`Uf64E11M52` and promotes its arithmetic through nightly primitive `f128`. |
| `nightly` | No | Convenience feature enabling both `f16` and `f128`. |
| `simd` | No | Enables nightly portable-SIMD bulk conversion and elementwise APIs for UF16/UF32. |
| `nightly` | No | Convenience feature enabling `f16`, `f128`, and `simd`. |
| `soft-float` | No | Forces the software/LUT dispatch path where available. If combined with `f16`, `soft-float` wins for `Uf8`. |

The default `Uf8` arithmetic path uses generated 256x256 lookup tables for
Expand All @@ -199,6 +201,42 @@ generated by `build.rs` into Cargo's `OUT_DIR` and embedded with
The library is `#![no_std]`. The build script and benchmarks use `std`, but the
crate API itself does not require allocation or the standard library.

## Development and release checks

The supported stable baseline is Rust 1.85. CI tests that baseline and current
stable on Linux, macOS, and Windows; compiles the default `no_std` API for
AArch64, RISC-V, and WebAssembly; and runs the nightly-only feature set
separately. It also packages the crate, extracts the archive, and reruns the
library, documentation, and documentation tests from the extracted source.

Before submitting a change, run the stable baseline checks:

```sh
cargo fmt --check
cargo clippy --lib --tests -- -D warnings
cargo test --lib
cargo test --doc
RUSTDOCFLAGS='-D warnings' cargo doc --no-deps
```

Changes to an opt-in nightly implementation should additionally run:

```sh
cargo +nightly test --all-features
RUSTDOCFLAGS='-D warnings' cargo +nightly doc --all-features --no-deps
```

Releases are tag-driven: GitHub release tag `vX.Y.Z` must name the exact
`Cargo.toml` version, and its current target must still match the immutable
commit SHA captured when that release was published; that commit must be
reachable from `main`. The release workflow repeats the extracted-package
verification before publishing through crates.io Trusted Publishing: it
exchanges the workflow's GitHub OIDC identity for a short-lived token rather
than storing a long-lived crates.io credential in the repository. Before the
first automated release, configure the crate's trusted publisher as GitHub
owner `MicroPerceptron`, repository `ufloat`, and workflow filename
`publish.yml`.

## Benchmarks

The benchmark suite uses nightly's built-in `test` harness and covers
Expand All @@ -214,6 +252,24 @@ Each command benchmarks the dispatch path selected by that feature set. For
example, `--features f16` measures the `Uf8` primitive-`f16` path, while the
default command measures the generated LUT path.

With nightly, `simd` adds allocation-free slice APIs that widen normal finite
UF16 lanes to native F32 and UF32 lanes to native F64. The bulk operators keep
the exact scalar result for every lane: blocks containing subnormals, NaNs,
infinities, negative results, or an encoding round-carry fall back to the
scalar conversion rather than changing the numeric contract.

```rust
use unsigned_float::{Uf16, simd};

let packed = [Uf16::from_f32(0.75); 256];
let mut linear = [0.0_f32; 256];
simd::decode_uf16_to_f32(&packed, &mut linear).unwrap();

let mut doubled = [Uf16::ZERO; 256];
simd::add_uf16(&packed, &packed, &mut doubled).unwrap();
assert_eq!(doubled[0].to_f32(), 1.5);
```

## Status

Implemented:
Expand All @@ -230,13 +286,14 @@ Implemented:
- `Add`, `Sub`, `Mul`, and `Div`
- raw-bit `Ord`/`PartialOrd`
- generated UF8 arithmetic and exponentiation lookup tables
- opt-in SIMD bulk conversion and arithmetic for UF16/UF32, with scalar-exact
edge-lane fallback
- benchmarks across conversions, arithmetic, and ordering

Still worth exploring:

- native baseline benchmarks against `f32` and `f64`
- configurable UF8 dispatch for direct LUT versus promote-to-native comparisons
- SIMD bulk operations
- more explicit NaN payload policy
- broader property tests for all finite `Uf16` layout edge cases

Expand Down
Loading