diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 184ecf1..3eee593 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.88 + - uses: dtolnay/rust-toolchain@1.95 - uses: Swatinem/rust-cache@v2 - run: cargo check --features std diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2d936e0..bcab3e2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,17 +1,60 @@ name: Publish +# Releases on merge to main: when Cargo.toml's version is not yet on +# crates.io, this workflow tests, tags, publishes, and creates the +# GitHub release in a single run. +# +# Everything happens in one workflow because tag pushes made with the +# default GITHUB_TOKEN deliberately do not trigger other workflows — +# that is what broke the old auto-tag.yml → publish.yml chain. Here the +# tag is an output of the run, not a trigger, so no PAT is needed. +# +# Every step is idempotent: an already-published version short-circuits +# the run, and an existing tag is left in place. A failed run can be +# retried from the Actions tab (or `gh workflow run publish.yml`). + on: push: - tags: - - 'v*.*.*' + branches: [main] + workflow_dispatch: env: CARGO_TERM_COLOR: always +concurrency: + group: publish + cancel-in-progress: false + jobs: - # Run full test suite before publishing + check: + name: Check release needed + runs-on: ubuntu-latest + outputs: + version: ${{ steps.check.outputs.version }} + release: ${{ steps.check.outputs.release }} + steps: + - uses: actions/checkout@v4 + - name: Compare Cargo.toml version against crates.io + id: check + run: | + VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + # The sparse index needs no auth and serves one JSON line per + # published version. A 404 means the crate has never been + # published, which also means "release". + if curl -sf "https://index.crates.io/fi/xe/fixed_analytics" \ + | jq -e --arg v "$VERSION" 'select(.vers == $v)' > /dev/null; then + echo "release=false" >> "$GITHUB_OUTPUT" + echo "v$VERSION is already on crates.io — nothing to release" + else + echo "release=true" >> "$GITHUB_OUTPUT" + echo "v$VERSION is not on crates.io — releasing" + fi + test: name: Pre-publish Tests + needs: check + if: needs.check.outputs.release == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -23,53 +66,54 @@ jobs: env: RUSTDOCFLAGS: -Dwarnings - # Publish to crates.io - publish: - name: Publish to crates.io - needs: test + release: + name: Tag, publish, and release + needs: [check, test] + if: needs.check.outputs.release == 'true' runs-on: ubuntu-latest + permissions: + contents: write + env: + VERSION: ${{ needs.check.outputs.version }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - - name: Verify version matches tag + + # Tag before publishing: a tag without a crates.io release heals + # on retry (the tag step skips, publish runs); the reverse order + # would leave an already-published version that the check job + # filters out, so the tag would never be created. + - name: Create tag if missing run: | - CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') - TAG_VERSION=${GITHUB_REF#refs/tags/v} - if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then - echo "Version mismatch: Cargo.toml has $CARGO_VERSION but tag is $TAG_VERSION" - exit 1 + if git ls-remote --tags origin "refs/tags/v$VERSION" | grep -q .; then + echo "Tag v$VERSION already exists, keeping it" + else + git tag "v$VERSION" + git push origin "v$VERSION" fi - - - name: Publish + + - name: Publish to crates.io run: cargo publish env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - # Create GitHub release with auto-generated notes - release: - name: Create GitHub Release - needs: publish - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - uses: softprops/action-gh-release@v1 + - name: Create GitHub release + uses: softprops/action-gh-release@v1 with: + tag_name: v${{ needs.check.outputs.version }} generate_release_notes: true body: | ## Installation - + ```toml [dependencies] - fixed_analytics = "${{ github.ref_name }}" + fixed_analytics = "${{ needs.check.outputs.version }}" ``` - + For `no_std` environments: - + ```toml [dependencies] - fixed_analytics = { version = "${{ github.ref_name }}", default-features = false } - ``` \ No newline at end of file + fixed_analytics = { version = "${{ needs.check.outputs.version }}", default-features = false } + ``` diff --git a/Cargo.toml b/Cargo.toml index 30ae99c..a1044c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "fixed_analytics" -version = "2.0.1" +version = "2.1.0" edition = "2024" -rust-version = "1.88" +rust-version = "1.95" authors = ["David Gathercole"] description = "Fixed-point mathematical functions. Accurate, deterministic, and panic free." repository = "https://github.com/GeEom/fixed_analytics" @@ -22,7 +22,7 @@ std = [] verify-no-panic = ["dep:no-panic"] [dependencies] -fixed = "1.30" +fixed = "1.31" no-panic = { version = "0.1", optional = true } [dev-dependencies] diff --git a/README.md b/README.md index 6e464e0..62f1357 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,11 @@ assert!((log.to_num::() - 1.0).abs() < 0.01); ## Installation -Requires Rust 1.88 or later. +Requires Rust 1.95 or later. ```toml [dependencies] -fixed_analytics = "2.0.1" +fixed_analytics = "2.1.0" ``` For `no_std` environments: @@ -82,9 +82,9 @@ Relative error statistics measured against MPFR reference implementations. Accur | sin | 6.06e-4 | 8.78e-5 | 1.28e-3 | 1.16e-8 | 1.68e-9 | 2.43e-8 | | cos | 6.45e-4 | 9.03e-5 | 1.38e-3 | 1.22e-8 | 1.72e-9 | 2.64e-8 | | tan | 7.20e-5 | 3.57e-5 | 2.20e-4 | 1.28e-9 | 3.98e-10 | 3.03e-9 | -| asin | 2.87e-4 | 5.93e-5 | 6.46e-4 | 5.34e-9 | 8.82e-10 | 1.03e-8 | -| acos | 3.61e-5 | 2.18e-5 | 1.14e-4 | 5.37e-10 | 3.19e-10 | 1.71e-9 | -| atan | 2.71e-5 | 2.21e-5 | 6.29e-5 | 3.69e-10 | 2.92e-10 | 8.74e-10 | +| asin | 1.85e-4 | 3.60e-5 | 4.75e-4 | 4.49e-9 | 7.35e-10 | 8.89e-9 | +| acos | 2.33e-5 | 1.47e-5 | 6.86e-5 | 4.51e-10 | 2.71e-10 | 1.38e-9 | +| atan | 1.50e-5 | 1.23e-5 | 3.45e-5 | 3.01e-10 | 2.44e-10 | 7.05e-10 | | sinh | 9.80e-5 | 6.23e-5 | 2.79e-4 | 1.52e-9 | 9.64e-10 | 4.29e-9 | | cosh | 9.40e-5 | 5.75e-5 | 2.77e-4 | 1.44e-9 | 8.90e-10 | 4.25e-9 | | tanh | 1.60e-5 | 1.32e-5 | 2.56e-5 | 2.25e-10 | 1.22e-10 | 3.90e-10 | @@ -93,10 +93,10 @@ Relative error statistics measured against MPFR reference implementations. Accur | acosh | 6.74e-4 | 5.21e-4 | 1.80e-3 | 1.05e-8 | 7.96e-9 | 2.88e-8 | | atanh | 3.01e-4 | 5.90e-5 | 6.25e-4 | 6.68e-9 | 1.32e-9 | 1.44e-8 | | acoth | 2.10e-3 | 1.33e-3 | 6.67e-3 | 4.26e-8 | 2.62e-8 | 1.39e-7 | -| exp | 1.14e-2 | 2.32e-5 | 7.88e-2 | 1.91e-7 | 1.73e-9 | 1.30e-6 | +| exp | 5.98e-3 | 1.47e-5 | 4.13e-2 | 9.49e-8 | 1.50e-9 | 6.50e-7 | | ln | 1.35e-5 | 8.76e-6 | 2.97e-5 | 4.50e-10 | 3.48e-10 | 9.17e-10 | | log2 | 1.33e-5 | 8.48e-6 | 2.92e-5 | 3.46e-10 | 2.24e-10 | 7.21e-10 | | log10 | 1.44e-5 | 9.28e-6 | 3.14e-5 | 4.49e-10 | 3.27e-10 | 9.07e-10 | -| pow2 | 7.21e-4 | 2.58e-5 | 4.72e-3 | 1.13e-8 | 4.93e-10 | 7.43e-8 | +| pow2 | 3.62e-4 | 2.24e-5 | 2.37e-3 | 5.64e-9 | 4.29e-10 | 3.67e-8 | | sqrt | 1.77e-7 | 1.16e-7 | 4.74e-7 | 2.70e-12 | 1.78e-12 | 7.16e-12 | \ No newline at end of file diff --git a/codecov.yml b/codecov.yml index b0f4e73..527f36d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,16 +4,16 @@ codecov: coverage: precision: 2 round: down - range: "70...100" + range: "90...100" status: project: default: - target: 80% + target: 95% threshold: 1% patch: default: - target: 80% + target: 85% comment: layout: "reach,diff,flags,files,footer" diff --git a/src/kernel/cordic.rs b/src/kernel/cordic.rs index fedfd3f..b1f42a9 100644 --- a/src/kernel/cordic.rs +++ b/src/kernel/cordic.rs @@ -41,6 +41,36 @@ const fn table_lookup(table: &[i64; 64], index: u32) -> i64 { table[index as usize] } +/// Converts an I1F63 table constant to `T`, rounding to nearest. +/// +/// `CordicNumber::from_i1f63` truncates, which biases every converted +/// angle low by up to one ulp. For circular CORDIC the accumulated z is +/// dominated by this table quantization, so rounding to nearest halves +/// the worst case and removes the systematic bias. +/// +/// Hyperbolic CORDIC deliberately keeps the truncating conversion: its +/// datapath shifts carry their own bias, which the truncated table +/// angles partially cancel (measured, not designed — see the accuracy +/// baseline history). +fn from_i1f63_rounded(bits: i64) -> T { + let truncated = T::from_i1f63(bits); + let frac = T::frac_bits(); + if frac >= 63 { + // Conversion is exact; nothing to round. + return truncated; + } + // The conversion discards the low (63 - frac) bits. If the highest + // discarded bit is set, the true value is at least half an output + // ulp above the truncated result, so round up by one ulp. + let shift = 63 - frac; + if (bits >> (shift - 1)) & 1 == 1 { + let ulp = T::one() >> frac; + truncated.saturating_add(ulp) + } else { + truncated + } +} + /// Performs circular CORDIC in vectoring mode. /// /// Given an initial vector (x, y), rotates it until y ≈ 0. @@ -68,7 +98,7 @@ pub fn circular_vectoring(mut x: T, mut y: T, mut z: T) -> (T, let iterations = T::frac_bits().min(62); for i in 0..iterations { - let angle = T::from_i1f63(table_lookup(&ATAN_TABLE, i)); + let angle = from_i1f63_rounded::(table_lookup(&ATAN_TABLE, i)); if y < zero { // y is negative, rotate counter-clockwise to bring y toward zero diff --git a/src/ops/exponential.rs b/src/ops/exponential.rs index 904a211..c5c30f1 100644 --- a/src/ops/exponential.rs +++ b/src/ops/exponential.rs @@ -5,6 +5,27 @@ use crate::error::{Error, Result}; use crate::ops::hyperbolic::atanh_open; use crate::traits::CordicNumber; +/// Right-shifts a non-negative value by `n`, rounding to nearest. +/// +/// A plain `>>` truncates, which biases results low by up to a full ulp. +/// That matters for the final scaling of `exp`, where the true result may +/// be only a few ulps: rounding halves the worst case and removes the bias. +fn shr_rounded(x: T, n: u32) -> T { + if n == 0 { + return x; + } + let ulp = T::one() >> T::frac_bits(); + let shifted = x >> n; + // Reconstruct the discarded low bits: rem ∈ [0, 2^n) ulps. + let rem = x.saturating_sub(shifted << n); + let half = ulp << (n - 1); + if rem >= half { + shifted.saturating_add(ulp) + } else { + shifted + } +} + /// Exponential function (e^x). /// /// # Saturation Behavior @@ -14,11 +35,12 @@ use crate::traits::CordicNumber; /// | Condition | Result | Example (I16F16) | /// |-----------|--------|------------------| /// | x > `ln(T::MAX)` | `T::MAX` | x > ~10.4 → 32767.99 | -/// | x < `ln(T::MIN_POSITIVE)` | `T::ZERO` | x < ~-11.1 → 0 | +/// | x < `ln(T::MIN_POSITIVE / 2)` | `T::ZERO` | x < ~-11.8 → 0 | /// +/// The zero threshold is where e^x rounds to nearest below half an ulp. /// The exact thresholds depend on the type's range: -/// - **I16F16:** Saturates to MAX for x > ~10.4, to zero for x < ~-11.1 -/// - **I32F32:** Saturates to MAX for x > ~21.5, to zero for x < ~-22.2 +/// - **I16F16:** Saturates to MAX for x > ~10.4, to zero for x < ~-11.8 +/// - **I32F32:** Saturates to MAX for x > ~21.5, to zero for x < ~-22.9 /// /// Saturation is silent and deterministic. If you need to detect overflow, /// check the input range before calling: @@ -48,15 +70,18 @@ pub fn exp(x: T) -> T { return one; } - // Argument reduction: exp(x) = 2^k * exp(r), where r ∈ (-ln2, ln2). - // Compute k = trunc(x / ln2) in one step, then r = x - k*ln2 in one - // subtraction. Truncation toward zero matches the old iterative - // subtraction behavior while avoiding error accumulation. + // Argument reduction: exp(x) = 2^k * exp(r), where r ∈ [0, ln2). + // Compute k = floor(x / ln2): start from the truncated quotient and + // adjust once if the remainder is negative (the quotient error is + // below one ulp, so a single adjustment suffices). Keeping r + // non-negative gives exp(r) ∈ [1, 2) — a full significand ahead of + // the final scaling shift. #[allow(clippy::cast_possible_wrap, reason = "total_bits bounded by type size")] let max_shift = (T::total_bits() - 1) as i32; - let scale = x.div(ln2).to_i32(); + let mut scale = x.div(ln2).to_i32(); - // Early exit for values that will saturate after scaling + // Early exit for values that will saturate after scaling. This also + // keeps `scale` small enough that scale * ln2 below cannot overflow. if scale > max_shift { return T::max_value(); } @@ -64,7 +89,14 @@ pub fn exp(x: T) -> T { return zero; } - let r = x.saturating_sub(T::from_num(scale).saturating_mul(ln2)); + let mut r = x.saturating_sub(T::from_num(scale).saturating_mul(ln2)); + if r < zero { + scale -= 1; + r = r.saturating_add(ln2); + if scale < -max_shift { + return zero; + } + } // Factored Taylor: exp(r) = 1 + r*(1 + r/2*(1 + r/3*(1 + ... r/n))) let mut p = one; @@ -89,21 +121,23 @@ pub fn exp(x: T) -> T { // Scale by 2^scale using bit shifts. // scale is already bounded to [-max_shift, max_shift] by the early exits above. - #[allow(clippy::cast_sign_loss, reason = "scale >= 0 checked before cast")] - match scale.cmp(&0) { - core::cmp::Ordering::Greater => { - let shift = scale as u32; - // Detect overflow before shifting: if exp_r > MAX >> shift, - // the left shift would wrap, so saturate to MAX instead. - let headroom = T::max_value() >> shift; - if exp_r > headroom { - T::max_value() - } else { - exp_r << shift - } + #[allow( + clippy::cast_sign_loss, + reason = "sign of scale checked before each cast" + )] + if scale > 0 { + let shift = scale as u32; + // Detect overflow before shifting: if exp_r > MAX >> shift, + // the left shift would wrap, so saturate to MAX instead. + let headroom = T::max_value() >> shift; + if exp_r > headroom { + T::max_value() + } else { + exp_r << shift } - core::cmp::Ordering::Less => exp_r >> ((-scale) as u32), - core::cmp::Ordering::Equal => exp_r, + } else { + // scale ≤ 0: rounded right shift (shr_rounded(x, 0) is the identity) + shr_rounded(exp_r, (-scale) as u32) } } @@ -201,11 +235,12 @@ pub fn log10(x: T) -> Result { /// | Condition | Result | Example (I16F16) | /// |-----------|--------|------------------| /// | x > `log2(T::MAX)` | `T::MAX` | x > ~15 → 32767.99 | -/// | x < `log2(T::MIN_POSITIVE)` | `T::ZERO` | x < ~-16 → 0 | +/// | x < `log2(T::MIN_POSITIVE / 2)` | `T::ZERO` | x < ~-17 → 0 | /// +/// The zero threshold is where 2^x rounds to nearest below half an ulp. /// The exact thresholds: -/// - **I16F16:** Saturates for x > ~15 or x < ~-16 -/// - **I32F32:** Saturates for x > ~31 or x < ~-32 +/// - **I16F16:** Saturates for x > ~15 or x < ~-17 +/// - **I32F32:** Saturates for x > ~31 or x < ~-33 #[must_use] #[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)] pub fn pow2(x: T) -> T { diff --git a/tests/unit/kernel/cordic.rs b/tests/unit/kernel/cordic.rs index b471400..617901d 100644 --- a/tests/unit/kernel/cordic.rs +++ b/tests/unit/kernel/cordic.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod tests { - use fixed::types::I16F16; + use fixed::types::{I16F16, I64F64}; use fixed_analytics::kernel::circular_vectoring; #[test] @@ -13,4 +13,17 @@ mod tests { let expected = core::f32::consts::FRAC_PI_4; assert!((z_f32 - expected).abs() < 0.01); } + + #[test] + fn circular_vectoring_high_precision_type() { + // I64F64 has 64 fractional bits, more than the I1F63 tables: + // exercises the exact (no rounding needed) conversion path. + let (_, _, z) = circular_vectoring(I64F64::ONE, I64F64::ONE, I64F64::ZERO); + let z_f64: f64 = z.to_num(); + let expected = core::f64::consts::FRAC_PI_4; + assert!( + (z_f64 - expected).abs() < 1e-15, + "atan(1) at I64F64 = {z_f64}, expected {expected}" + ); + } } diff --git a/tests/unit/ops/exponential.rs b/tests/unit/ops/exponential.rs index 3bbdfd8..b62a74a 100644 --- a/tests/unit/ops/exponential.rs +++ b/tests/unit/ops/exponential.rs @@ -285,8 +285,9 @@ mod tests { } // ===== exp saturation thresholds ===== - // I16F16: saturates to MAX at x >= 10.4, to zero at x <= -11.1 - // I32F32: saturates to MAX at x >= 21.49, to zero at x <= -22.2 + // The result is zero once e^x rounds to nearest below half an ulp. + // I16F16: saturates to MAX at x >= 10.4, to zero at x <= -11.8 + // I32F32: saturates to MAX at x >= 21.49, to zero at x <= -22.9 #[test] fn exp_i16f16_upper_threshold() { @@ -304,15 +305,32 @@ mod tests { #[test] fn exp_i16f16_lower_threshold() { - // Above threshold: should NOT be zero + // Above threshold: rounds to one ulp, should NOT be zero assert!( - !is_zero_16(exp(I16F16::from_num(-11.0))), - "exp(-11.0) should not be zero" + !is_zero_16(exp(I16F16::from_num(-11.7))), + "exp(-11.7) should not be zero" ); - // At threshold: should be zero + // At threshold: rounds below half an ulp, should be zero assert!( - is_zero_16(exp(I16F16::from_num(-11.1))), - "exp(-11.1) should be zero" + is_zero_16(exp(I16F16::from_num(-11.8))), + "exp(-11.8) should be zero" + ); + } + + #[test] + fn exp_deep_negative_band_is_zero() { + // Exercises the floor-adjustment path where the truncated + // quotient sits exactly at -max_shift and the adjustment + // pushes it past: trunc(-21.6/ln2) = -31 = -max_shift for + // I16F16, then r < 0 decrements scale to -32. + assert!( + is_zero_16(exp(I16F16::from_num(-21.6))), + "exp(-21.6) should be zero" + ); + // Same band for I32F32: trunc(-43.7/ln2) = -63 = -max_shift. + assert!( + is_zero_32(exp(I32F32::from_num(-43.7))), + "exp(-43.7) should be zero" ); } @@ -332,21 +350,22 @@ mod tests { #[test] fn exp_i32f32_lower_threshold() { - // Above threshold: should NOT be zero + // Above threshold: rounds to one ulp, should NOT be zero assert!( - !is_zero_32(exp(I32F32::from_num(-22.1))), - "exp(-22.1) should not be zero" + !is_zero_32(exp(I32F32::from_num(-22.8))), + "exp(-22.8) should not be zero" ); - // At threshold: should be zero + // At threshold: rounds below half an ulp, should be zero assert!( - is_zero_32(exp(I32F32::from_num(-22.2))), - "exp(-22.2) should be zero" + is_zero_32(exp(I32F32::from_num(-22.9))), + "exp(-22.9) should be zero" ); } // ===== pow2 saturation thresholds ===== - // I16F16: saturates to MAX at x >= 15.0, to zero at x <= -16.1 - // I32F32: saturates to MAX at x >= 31.0, to zero at x <= -32.1 + // The result is zero once 2^x rounds to nearest below half an ulp. + // I16F16: saturates to MAX at x >= 15.0, to zero at x <= -17.1 + // I32F32: saturates to MAX at x >= 31.0, to zero at x <= -33.1 #[test] fn pow2_i16f16_upper_threshold() { @@ -364,15 +383,15 @@ mod tests { #[test] fn pow2_i16f16_lower_threshold() { - // Above threshold: should NOT be zero + // Above threshold: rounds to one ulp, should NOT be zero assert!( - !is_zero_16(pow2(I16F16::from_num(-16.0))), - "pow2(-16.0) should not be zero" + !is_zero_16(pow2(I16F16::from_num(-16.9))), + "pow2(-16.9) should not be zero" ); - // At threshold: should be zero + // At threshold: rounds below half an ulp, should be zero assert!( - is_zero_16(pow2(I16F16::from_num(-16.1))), - "pow2(-16.1) should be zero" + is_zero_16(pow2(I16F16::from_num(-17.1))), + "pow2(-17.1) should be zero" ); } @@ -392,15 +411,15 @@ mod tests { #[test] fn pow2_i32f32_lower_threshold() { - // Above threshold: should NOT be zero + // Above threshold: rounds to one ulp, should NOT be zero assert!( - !is_zero_32(pow2(I32F32::from_num(-32.0))), - "pow2(-32.0) should not be zero" + !is_zero_32(pow2(I32F32::from_num(-32.9))), + "pow2(-32.9) should not be zero" ); - // At threshold: should be zero + // At threshold: rounds below half an ulp, should be zero assert!( - is_zero_32(pow2(I32F32::from_num(-32.1))), - "pow2(-32.1) should be zero" + is_zero_32(pow2(I32F32::from_num(-33.1))), + "pow2(-33.1) should be zero" ); } } diff --git a/tools/accuracy-bench/Cargo.toml b/tools/accuracy-bench/Cargo.toml index 48f753e..ae69b35 100644 --- a/tools/accuracy-bench/Cargo.toml +++ b/tools/accuracy-bench/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] fixed_analytics = { path = "../.." } -fixed = "1.30" +fixed = "1.31" rug = "1.28" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/tools/accuracy-bench/baseline.json b/tools/accuracy-bench/baseline.json index 922dd38..4a1181d 100644 --- a/tools/accuracy-bench/baseline.json +++ b/tools/accuracy-bench/baseline.json @@ -1,5 +1,5 @@ { - "timestamp": 1773684228, + "timestamp": 1783329055, "results": [ { "name": "sin", @@ -95,29 +95,29 @@ "name": "asin", "i16f16": { "count": 59003, - "abs_max": 0.00023649662451963316, - "abs_mean": 0.00003714418405560642, - "abs_p50": 0.00003117154950399437, - "abs_p95": 0.00009224021176928998, - "abs_p99": 0.00012254361001007297, - "rel_max": 0.34137995039935165, - "rel_mean": 0.0002874519036260217, - "rel_p50": 0.00005927056289217883, - "rel_p95": 0.0006463889755348835, - "rel_p99": 0.0036186307235384573 + "abs_max": 0.0001245804871132794, + "abs_mean": 0.000023135371505940558, + "abs_p50": 0.00001981861728889145, + "abs_p95": 0.000055935129377848725, + "abs_p99": 0.00007413155549662598, + "rel_max": 0.2286483921083974, + "rel_mean": 0.0001848477641879009, + "rel_p50": 0.00003599816038099953, + "rel_p95": 0.00047494272931951847, + "rel_p99": 0.0023205264290453286 }, "i32f32": { "count": 59003, - "abs_max": 3.122051062121045e-9, - "abs_mean": 5.602827229871175e-10, - "abs_p50": 4.745828730001733e-10, - "abs_p95": 1.3696153100539732e-9, - "abs_p99": 1.8091814157905617e-9, - "rel_max": 9.869671335247343e-6, - "rel_mean": 5.339951862962058e-9, - "rel_p50": 8.822269498818044e-10, - "rel_p95": 1.0322344952896748e-8, - "rel_p99": 5.106086035100781e-8 + "abs_max": 2.64860694487723e-9, + "abs_mean": 4.734204961464557e-10, + "abs_p50": 4.015949794933249e-10, + "abs_p95": 1.1598155769121377e-9, + "abs_p99": 1.5267173636424047e-9, + "rel_max": 6.585652811427218e-6, + "rel_mean": 4.491789537472936e-9, + "rel_p50": 7.349755952360814e-10, + "rel_p95": 8.887621226836101e-9, + "rel_p99": 4.305661252158066e-8 }, "samples_tested": 59003 }, @@ -125,29 +125,29 @@ "name": "acos", "i16f16": { "count": 59003, - "abs_max": 0.00022569229056043638, - "abs_mean": 0.000038483916449861316, - "abs_p50": 0.000032307306703560634, - "abs_p95": 0.00009483269499988012, - "abs_p99": 0.00012600751238389662, - "rel_max": 0.0015834106598614664, - "rel_mean": 0.000036074766711138575, - "rel_p50": 0.000021798839245986088, - "rel_p95": 0.00011378061092625136, - "rel_p99": 0.0002626829303430327 + "abs_max": 0.0001345724505834589, + "abs_mean": 0.000025194457450702663, + "abs_p50": 0.000021601056192421808, + "abs_p95": 0.00006071819610875551, + "abs_p99": 0.00007890516076436427, + "rel_max": 0.0007606273037416324, + "rel_mean": 0.000023312629588548, + "rel_p50": 0.000014741930162677303, + "rel_p95": 0.00006857606436282256, + "rel_p99": 0.00018481621520396088 }, "i32f32": { "count": 59003, - "abs_max": 3.1356299778906305e-9, - "abs_mean": 5.638112468372049e-10, - "abs_p50": 4.769011852090443e-10, - "abs_p95": 1.3800334208724507e-9, - "abs_p99": 1.8252424016651503e-9, - "rel_max": 1.511695277515043e-8, - "rel_mean": 5.37222247227645e-10, - "rel_p50": 3.190547944851976e-10, - "rel_p95": 1.7112165219282182e-9, - "rel_p99": 4.16890407436705e-9 + "abs_max": 2.637457363618978e-9, + "abs_mean": 4.771190947426562e-10, + "abs_p50": 4.0556669134161893e-10, + "abs_p95": 1.1676429267915012e-9, + "abs_p99": 1.5423831101202268e-9, + "rel_max": 1.4374429727514298e-8, + "rel_mean": 4.513133469796149e-10, + "rel_p50": 2.711501137985831e-10, + "rel_p95": 1.3836960810872683e-9, + "rel_p99": 3.586759154363366e-9 }, "samples_tested": 59003 }, @@ -155,29 +155,29 @@ "name": "atan", "i16f16": { "count": 59007, - "abs_max": 0.00017964359347133474, - "abs_mean": 0.00003898350652921342, - "abs_p50": 0.00003344140314665012, - "abs_p95": 0.00009394273117457885, - "abs_p99": 0.00012177656344758425, - "rel_max": 0.005249545623631684, - "rel_mean": 0.0000270794350471048, - "rel_p50": 0.000022060116939467474, - "rel_p95": 0.00006288436846270004, - "rel_p99": 0.00008743059742712145 + "abs_max": 0.00009635842086969104, + "abs_mean": 0.000021516147001383534, + "abs_p50": 0.000018578354218812265, + "abs_p95": 0.000051847307452890234, + "abs_p99": 0.00006198004229185372, + "rel_max": 0.00261310317711038, + "rel_mean": 0.000014991770491118831, + "rel_p50": 0.000012255592507336696, + "rel_p95": 0.00003453497462890847, + "rel_p99": 0.00004411362592735666 }, "i32f32": { "count": 59007, - "abs_max": 2.6864772628698574e-9, - "abs_mean": 5.257286334552021e-10, - "abs_p50": 4.420290800055682e-10, - "abs_p95": 1.2914966873722733e-9, - "abs_p99": 1.7201167157310238e-9, - "rel_max": 8.01494739939048e-8, - "rel_mean": 3.6947455314372755e-10, - "rel_p50": 2.9200208584749607e-10, - "rel_p95": 8.736222188657199e-10, - "rel_p99": 1.2362141835755569e-9 + "abs_max": 2.249368913354033e-9, + "abs_mean": 4.313711147886401e-10, + "abs_p50": 3.6981440132421994e-10, + "abs_p95": 1.0434733077602232e-9, + "abs_p99": 1.3546948007814308e-9, + "rel_max": 4.0305768754078694e-8, + "rel_mean": 3.00707401822319e-10, + "rel_p50": 2.4352410123890363e-10, + "rel_p95": 7.052196144414705e-10, + "rel_p99": 9.914488443352585e-10 }, "samples_tested": 59007 }, @@ -426,28 +426,28 @@ "i16f16": { "count": 59007, "abs_max": 0.05963610196704394, - "abs_mean": 0.00148284412568926, - "abs_p50": 0.000013067910371533951, + "abs_mean": 0.0014805267968587314, + "abs_p50": 6.761244257145094e-6, "abs_p95": 0.009531146223707765, "abs_p99": 0.02852926914965792, - "rel_max": 0.33333805248595344, - "rel_mean": 0.011412784731031914, - "rel_p50": 0.00002318455857263073, - "rel_p95": 0.07877109889269186, - "rel_p99": 0.18364178877011175 + "rel_max": 0.1426740720154479, + "rel_mean": 0.0059767261472860505, + "rel_p50": 0.000014718715219339471, + "rel_p95": 0.041324683442556215, + "rel_p99": 0.1002149610842742 }, "i32f32": { "count": 59007, "abs_max": 6.483619472419377e-6, - "abs_mean": 3.886059666467785e-7, - "abs_p50": 2.322268084211862e-10, + "abs_mean": 3.88562406241636e-7, + "abs_p50": 1.3134879989218362e-10, "abs_p95": 3.0078297186264535e-6, "abs_p99": 5.427600171969971e-6, - "rel_max": 5.084182472060312e-6, - "rel_mean": 1.9130050918738928e-7, - "rel_p50": 1.7319438978244154e-9, - "rel_p95": 1.2962127732347275e-6, - "rel_p99": 3.2225748664160253e-6 + "rel_max": 2.5523699287012625e-6, + "rel_mean": 9.494302233140087e-8, + "rel_p50": 1.496316945296044e-9, + "rel_p95": 6.502199966566667e-7, + "rel_p99": 1.5722149494883194e-6 }, "samples_tested": 59007 }, @@ -546,28 +546,28 @@ "i16f16": { "count": 59007, "abs_max": 0.03599762646047111, - "abs_mean": 0.0016799025276145177, - "abs_p50": 0.000018813379432325306, + "abs_mean": 0.0016775176582259166, + "abs_p50": 0.000013877321717092883, "abs_p95": 0.011321668885898362, "abs_p99": 0.021123637255755057, - "rel_max": 0.01538737068576766, - "rel_mean": 0.0007205229655744091, - "rel_p50": 0.000025828291700624078, - "rel_p95": 0.004715341194879804, - "rel_p99": 0.01027006938792182 + "rel_max": 0.0077695504551113705, + "rel_mean": 0.00036235207448079006, + "rel_p50": 0.00002235167318684783, + "rel_p95": 0.002371254364313451, + "rel_p99": 0.0051129784279687444 }, "i32f32": { "count": 59007, "abs_max": 6.981339311096235e-7, - "abs_mean": 3.477602797068013e-8, - "abs_p50": 3.6119718327398687e-10, + "abs_mean": 3.473772986110968e-8, + "abs_p50": 2.9091529185620857e-10, "abs_p95": 2.3425616291206097e-7, "abs_p99": 4.4821740630140994e-7, - "rel_max": 2.3427334467174056e-7, - "rel_mean": 1.1309086723812138e-8, - "rel_p50": 4.933149661779767e-10, - "rel_p95": 7.428902583432353e-8, - "rel_p99": 1.5696090047105027e-7 + "rel_max": 1.1767643954135106e-7, + "rel_mean": 5.64457795497978e-9, + "rel_p50": 4.286921741160076e-10, + "rel_p95": 3.670519117768169e-8, + "rel_p99": 7.882663544368055e-8 }, "samples_tested": 59007 },