-
Notifications
You must be signed in to change notification settings - Fork 360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Miri float ops are less precise than what the standard library expects #4208
Comments
IEEE 754 § 9.2.1 defines several special cases, including |
Does it also define |
No. |
So some of the |
Actually, scratch that. §9.2 says “a conforming operation shall return results correctly rounded for the applicable rounding direction for all operands in its domain”, which implies that technically, to be conforming an implementation can’t have any error ever. Of course, actual implementations fail this, which means they technically aren’t conforming. I think that, beyond the special values explicitly called out in §9.2.1, some of this stuff (like |
§9.2 covers which operations? |
All the optional mathematical operations. (The required math ops have a similar proviso) |
Ah, lol. Does anyone actually do this right? Even @eduardosm's softfloat implementations had 1 ULP of error. Well, the goal of this non-determinism is to expose bugs in code that incorrectly relies on precision assumptions. I'll happily take the advice of a domain expert for what is the best strategy for that. :) |
The C standard is explicitly more forgiving, so maybe we should rely on it instead? (Also, I’m not a domain expert, I just read the standard!) |
Since we bind the C functions, we wind up following the same rules. Aiui this is extremely forgiving, Representing this well in Miri is tricky because, while it's technically allowed, an implementation that can't produce @beetrees may also have some ideas here.
There are some implementations that provide the |
FWIW results of 0 remain precise with the current scheme, since we add a relative error not an absolute one. (No idea what you meant by |
Note that some of these are doctests and hence can be read as guarantees that the implementation will be within |
Whoops, that should have been
I don't really like that we do this either, it builds the wrong intuition that Is it possible to enable the randomness for most things but exclude tests in |
That's tricky. We could add a flag to disable that and just set that flag for the entire "run std tests in Miri" run, though. But I want to first experiment with reducing the error Miri adds, e.g. to 4 ULP. |
Oh, that actually has some quite trivial points in it: "For each single-argument function f in <math.h> whose mathematical counterpart is symmetric I think LLVM actually violates this, since the result is effectively non-deterministic (if LLVM evaluates libm calls at compile-time), and therefore cannot satisfy that requirement. But then F.10.1-3 has a nice list of guarantees that are being made. I would add to that that functions with a clearly defined output range (such as @LorrensP-2158466 do you want to give this a try? Unfortunately I had to disable the code you added in your first PR as it made a bunch of test fail; turns out this is more complicated than I thought. I would say in a first PR, deal just with the ones defined in |
Yes, I'd like to give it a try, and just to be sure you want me to:
Am I missing something in this thread? |
Yes, please also make sure the guarantees listed in F.10.1-3 of the C standard are met. |
Alright, will do! |
My read of the standard was that F.10 paragraph 16 walks back the statement in paragraph 3:
The "should" wording here seems to imply that the implementations are not required to uphold symmetry/monotonicity/periodicy. Likely not a problem, in any case. |
What the heck, why would you have paragraph 16 directly contradict what paragraph 3 says?!? Anyway, we'll just ignore both of them then. 🤷 |
I don’t see a contradiction. Paragraph 3 specifies a subset of IEEE 754 requirements that C also requires, paragraph 16 notes that this subset is a proper subset. |
How do you think I should best implement this? I'm currently looking at this piece of code: | "sinf32"
| "cosf32"
| "expf32"
| "exp2f32"
| "logf32"
| "log10f32"
| "log2f32"
=> {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let host = f.to_host();
let res = match intrinsic_name {
"sinf32" => host.sin(),
"cosf32" => host.cos(),
"expf32" => host.exp(),
"exp2f32" => host.exp2(),
"logf32" => host.ln(),
"log10f32" => host.log10(),
"log2f32" => host.log2(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
// FIXME: temporarily disabled as it breaks std tests.
// let res = apply_random_float_error_ulp(
// this,
// res,
// 4, // log2(16)
// );
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
} For example Should I:
|
Tons of standard library float tests for ln, exp, and trigonometric functions fail with Miri's float non-determinism since they expect more precise results than what Miri delivers. I have temporarily disabled float non-determinism while we decide what to do.
A bunch of cases actually expect precise answers for certain cases, e.g.:
https://github.com/rust-lang/rust/blob/29166cd61711776e6d43239d6d18a0eafe6515b1/library/std/tests/floats/f64.rs#L468
https://github.com/rust-lang/rust/blob/29166cd61711776e6d43239d6d18a0eafe6515b1/library/std/tests/floats/f64.rs#L482-L483
Furthermore, there are quite a few cases where the standard library expects more precision than what Miri provides.
assert_approx_eq!
checks for a divergence of up to1e-6
, which is less than 10 ULP on a value of 1.0 represented asf32
. Miri currently adds 16 ULP of error (on top of whatever error the host libm we use to implement these function has). Some of these tests chain multiple operations, such as60.0f64.sinh().asinh()
, so the error accumulates; some of these tests use values sufficiently larger than 1 that 16 ULP end up even exceeding1e-5
. Furthermore, some doctests actually check that the precision is withinf32::EPSILON
, which is 1 ULP on a value of 1.0; that is obviously never going to fly if Miri adds any kind of non-deterministic error.I am open to reducing the error Miri applies, and happy for suggestions of what would make a good error scale. (I'd prefer it to be a power of 2, that makes the logic a lot simpler in Miri. ;) However, I also think some of these tests should change, specifically all tests that require either full equality or just 1 ULP error.
@tgross35 @Jules-Bertholet thoughts, opinions?
The text was updated successfully, but these errors were encountered: