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
11 changes: 11 additions & 0 deletions .github/workflows/drop_incompatible_deps_for_msrv.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@
'', text)
open('Cargo.toml', 'w').write(text)

# Strip rand 0.9 / 0.10: rand 0.10 requires Rust 1.85, and its feature
# table (getrandom as an optional dep with the `dep:` syntax) breaks the
# resolver under the 1.68 build. The MSRV build only exercises `rand`
# (== rand_v08); rand_v09 and rand_v010 are covered by the stable / 1.85
# `--all-features` jobs.
for manifest in ['Cargo.toml', 'integer/Cargo.toml', 'float/Cargo.toml', 'rational/Cargo.toml']:
text = open(manifest).read()
text = re.sub(r'^rand_v09 = .*\n', '', text, flags=re.MULTILINE)
text = re.sub(r'^rand_v010 = .*\n', '', text, flags=re.MULTILINE)
open(manifest, 'w').write(text)

# Drop the postgres submodule (its cfg-gated children reference
# the features we just removed).
text = open('float/src/third_party/mod.rs').read()
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Keep the `## Unreleased` section updated as you go.
## dashu-float internals

- Estimating the number of digits can be costly — prefer using `log2_bounds` and `repr.digits_ub`/`digits_lb` instead of computing exact digit counts.
- The number of digits in an `FBig` floating point number must always be less than or equal to the context precision. This invariant can be temporarily violated during internal calculations, where functions on the `Context` type should be used instead of the public API.
- The number of digits in an `FBig` significand is at most the context precision, with one intentional exception: the result of an inexact addition or subtraction may carry a single **guard digit** (up to `precision + 1` digits). During internal calculations the bound can be violated more freely; use the methods on `Context` instead of the public API in that case.

## dashu-int internals

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ zeroize = ["dashu-int/zeroize", "dashu-float/zeroize", "dashu-ratio/zeroize"]
# unstable features
rand = ["dashu-int/rand", "dashu-float/rand", "dashu-ratio/rand"]
rand_v08 = ["dashu-int/rand_v08", "dashu-float/rand_v08", "dashu-ratio/rand_v08"]
rand_v09 = ["dashu-int/rand_v09", "dashu-float/rand_v09", "dashu-ratio/rand_v09"]
rand_v010 = ["dashu-int/rand_v010", "dashu-float/rand_v010", "dashu-ratio/rand_v010"]
num-traits = ["dashu-int/num-traits", "dashu-float/num-traits", "dashu-ratio/num-traits"]
num-traits_v02 = ["dashu-int/num-traits_v02", "dashu-float/num-traits_v02", "dashu-ratio/num-traits_v02"]

Expand Down
6 changes: 6 additions & 0 deletions float/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@
- Add π constant computation (`FBig::pi()` and `Context::pi()`) using the Chudnovsky algorithm with binary splitting ([#60](https://github.com/cmpute/dashu/pull/60)).
- Add `FpResult` enum to handle non-finite math operation results (NaN, Infinite, Overflow, Underflow) without panicking ([#60](https://github.com/cmpute/dashu/pull/60)).
- Add `panic_nan`, `panic_overflow`, `panic_underflow`, and `panic_infinite` helpers to the `error` module.
- Optional `rand_v09` (rand 0.9, MSRV 1.63) and `rand_v010` (rand 0.10, MSRV 1.85) features mirroring `rand_v08`, exposing the random-float distributions (`Uniform01`, `UniformFBig`) under those rand versions. The default `rand` feature remains `rand_v08`.
- The `rand` distributions and their sampling algorithms now live once in the version-agnostic `dashu_float::rand` module. The `rand_v08` / `rand_v09` / `rand_v010` modules are now private per-version trait bindings; access the distributions through `dashu_float::rand`.

### Fix
- Fix rounding issues in `to_32()` and `to_f64()` (fixes [#53](https://github.com/cmpute/dashu/issues/53) and [#56](https://github.com/cmpute/dashu/issues/56)).
- Fix `FBig::fract()` inflating context precision for values smaller than one.
- Fix `split_at_point_internal` using incorrect fractional scale for numbers smaller than one, causing incorrect rounding results.
- Fix severe-cancellation errors in `FBig`/`Context` subtraction (and effective subtraction via addition): when the smaller operand reached into the larger operand's significant digits, the limited-precision alignment path could collapse a genuinely small difference to the wrong value (e.g. `1.00 - 0.99999999` at precision 3 returned `0` instead of `1e-8`). Such cases now form the exact difference at full operand width and round once.
- Fix a spurious-ULP rounding error in `FBig`/`Context` addition and subtraction when the larger operand had fewer digits than the context precision and the smaller operand was negligible (e.g. `1 + 2^-100` at precision 10 returned `513·2^-9` instead of `1` under base-2 round-to-nearest). The negligible-operand sticky is now positioned at the operand's real magnitude instead of at `precision - digits`, so it can no longer land on a rounding tie.
- Fix severe-cancellation errors at the window-edge boundary in `FBig`/`Context` subtraction: the guard fired only on strict overlap, missing the exact-edge case where the smaller operand's top digit lands on the precision-window edge, so a subtraction could still collapse (e.g. `0.5 - 0.4375 = 0.0625` at precision 1 returned `0`). The guard now fires on `>=`, i.e. whenever the smaller operand reaches the window; mild borrows there now return the exact `precision+1`-digit value (the guard digit), consistent with the documented add/sub precision behavior.
- Fix `Context::sub` mis-rounding when the left operand is zero under the asymmetric directed rounding modes (`Up` = toward +∞, `Down` = toward −∞). The zero-left path rounded the right operand and then negated, but `round(-x) != -round(x)` for those modes, so `0 - b` could land one ULP off (truncated toward zero instead of rounded away). The negated operand is now rounded directly.

## 0.4.4

Expand Down
12 changes: 11 additions & 1 deletion float/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ num-traits = ["num-traits_v02"]
num-traits_v02 = ["dep:num-traits_v02", "dashu-int/num-traits_v02"]
rand = ["rand_v08"]
rand_v08 = ["dep:rand_v08", "dashu-int/rand_v08"]
rand_v09 = ["dep:rand_v09", "dashu-int/rand_v09"]
rand_v010 = ["dep:rand_v010", "dashu-int/rand_v010"]
postgres-types = ["postgres-types_v02"]
postgres-types_v02 = ["dep:postgres-types_v02", "dep:_bytes", "std"]

Expand All @@ -45,17 +47,21 @@ num-order = { optional = true, version = "1.2.0", default-features = false }
serde = { optional = true, version = "1.0.130", default-features = false }
zeroize = { optional = true, version = "1.5.7", default-features = false }
diesel_v1 = { optional = true, version = "1.4.0", package = "diesel", default-features = false, features = ["postgres"]}
diesel_v2 = { optional = true, version = "2.0.0", package = "diesel", default-features = false, features = ["postgres_backend"]}
diesel_v2 = { optional = true, version = "2", package = "diesel", default-features = false, features = ["postgres_backend"]}
_bytes = { optional = true, version = "1.0", package = "bytes", default-features = false }

# unstable dependencies
rand_v08 = { optional = true, version = "0.8.3", package = "rand", default-features = false }
rand_v09 = { optional = true, version = "0.9", package = "rand", default-features = false }
rand_v010 = { optional = true, version = "0.10", package = "rand", default-features = false }
num-traits_v02 = { optional = true, version = "0.2.15", package = "num-traits", default-features = false }
postgres-types_v02 = { optional = true, version = "0.2.4", package = "postgres-types", default-features = false }
_num-modular = { optional = true, version = "0.6.1", package = "num-modular", default-features = false }

[dev-dependencies]
rand_v08 = { version = "0.8.3", package = "rand" }
rand_v09 = { version = "0.9", package = "rand" }
rand_v010 = { version = "0.10", package = "rand" }
postcard = { version = "1.0.2", features = ["alloc"] }
serde_test = { version = "1.0.130" }
serde_json = { version = "1.0" }
Expand Down Expand Up @@ -93,3 +99,7 @@ harness = false
name = "io"
required-features = ["rand"]
harness = false

[[example]]
name = "random_fbig"
required-features = ["rand"]
37 changes: 37 additions & 0 deletions float/examples/random_fbig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Generating random `FBig` values with the default rand version (0.8).
//!
//! This is the single runnable demonstration of the `dashu_float::rand` distributions (it
//! replaces the per-version examples that used to live on the `rand_vXX` modules).
//!
//! Run with: `cargo run --example random_fbig --features rand`

use dashu_float::rand::Uniform01;
use dashu_float::FBig;
use rand_v08::{
distributions::{Open01, OpenClosed01, Standard},
rngs::StdRng,
Rng, SeedableRng,
};

fn main() {
let mut rng = StdRng::seed_from_u64(0x1234_5678_9abc_def0);

// Uniform01 generates an FBig in [0, 1) at a precision you choose (here 20 bits).
let a: FBig = rng.sample(Uniform01::new(20));
let b: FBig = rng.sample(Uniform01::new_closed(20));
println!("a = {a} (Uniform01, [0, 1), precision 20)");
println!("b = {b} (Uniform01 closed, [0, 1], precision 20)");

// The builtin rand distributions pick the largest precision that fits a DoubleWord, so no
// allocation is needed: Standard -> [0, 1), Open01 -> (0, 1), OpenClosed01 -> (0, 1].
let c: FBig = rng.sample(Standard);
let d: FBig = rng.sample(Open01);
let e: FBig = rng.sample(OpenClosed01);
println!("c = {c} (Standard, [0, 1))");
println!("d = {d} (Open01, (0, 1))");
println!("e = {e} (OpenClosed01, (0, 1])");

// `rng.gen()` is sugar for sampling with `Standard`.
let f: FBig = rng.gen();
println!("f = {f} (rng.gen(), [0, 1))");
}
Loading
Loading