Conversation
|
Think this is step 1 to getting this stuff const. |
|
Ideally we would have the fuzz tests for these too to get better coverage, in fuzz/ops.rs. Unfortunately we can't test against LLVM's apfloat here given it doesn't exist, but I think it would be reasonable enough to test against the host For the remaining semantics (x86, ppc, etc), I'm not sure what to do. Unless there is a reasonable way to test then I'd almost say it's better to panic than to have them untested, any thoughts Ralf? |
tests/ieee.rs
Outdated
| } | ||
|
|
||
| #[test] | ||
| fn sqrt() { |
There was a problem hiding this comment.
Now you're only testing Double, i.e., f64. Since this is all softfloats, you should be able to test f16, f32, f128 as well entirely on stable. Look around in the test file for any infrastructure for generic tests, I don't know what (if anything) exists in that regard.
There was a problem hiding this comment.
Actually, Double tests seem pretty common. Eh, I don't know how the tests here work. I'll leave this to @tgross35 :)
Not really, I don't know apfloat at all.^^ I like conservative panics. |
src/lib.rs
Outdated
| // preserve zero sign | ||
| Category::Zero => self, | ||
| // propagate NaN | ||
| Category::NaN => self, |
There was a problem hiding this comment.
idk if it matters, but technically you're supposed to quiet signalling NaNs.
There was a problem hiding this comment.
If the input is a signalling NaN, then IEEE 754 requires the result to be converted to a quiet NaN. On most CPUs that means the most significant bit of the significand field is 0 for signalling NaNs and 1 for quiet NaNs. On most CPUs they quiet a NaN by setting that bit to a 1, RISC-V instead returns the canonical NaN with positive sign, the most significant significand bit set and all other significand bits cleared.
However, Rust and LLVM allow input NaNs to be returned unmodified as well as a few other options -- see Rust's rules for NaNs.
There was a problem hiding this comment.
okay so i think returning self is fine then.
(Do you mind if i copy this comment and put it where NaN is?)
There was a problem hiding this comment.
okay so i think returning self is fine then. (Do you mind if i copy this comment and put it where NaN is?)
sure, go ahead. you'll probably want to change the linked part to instead have the link's url on a line by itself
There was a problem hiding this comment.
This crate otherwise does a good job of sticking to IEEE semantics and quietens sNaNs elsewhere. I think we should do the same here - it's easy enough, just use result_from_nan like the other ops.
What do you mean by that?
Welcome to the world of floating point 🙃.
Crash course: there are two kinds of NaN, signaling (pretty useless and extremely annoying, but part of the spec) and quiet (used pretty much everywhere; this is Rust's f32::NAN). IEEE 754 (usually) says that when you pass a sNaN to an operation, it should "quiet" the sNaN or turn it into a qNaN, then set the invalid op exception.
You can see the exceptions in Rust, via asm, or in C:
src/lib.rs
Outdated
| } | ||
| } | ||
|
|
||
| fn sqrt(self) -> Self { |
There was a problem hiding this comment.
IEEE sqrt can take rounding mode and raise the floating point exceptions, so technically this should return a StatusAnd. I think it's fine to always round to nearest and disregard exceptions, but please document this.
There was a problem hiding this comment.
you can check the comment i added to see if its what you want
There was a problem hiding this comment.
Currently:
/// Technically this should be StatusAnd<Self>, but since sqrt is exact for all supported formats,
/// we can just round to the nearest, ignore exceptions, and return Self.
The reasoning isn't correct: exact means correctly rounded, it doesn't mean no rounding will occur. You can have exact results with any of the four rounding modes. Instead it should just say that we don't support this.
Please also note that this is public documentation so the first line should just say what the function is before jumping into its details. Also please quote code in `...`.
src/lib.rs
Outdated
| // preserve zero sign | ||
| Category::Zero => self, | ||
| // propagate NaN | ||
| Category::NaN => self, |
There was a problem hiding this comment.
This crate otherwise does a good job of sticking to IEEE semantics and quietens sNaNs elsewhere. I think we should do the same here - it's easy enough, just use result_from_nan like the other ops.
What do you mean by that?
Welcome to the world of floating point 🙃.
Crash course: there are two kinds of NaN, signaling (pretty useless and extremely annoying, but part of the spec) and quiet (used pretty much everywhere; this is Rust's f32::NAN). IEEE 754 (usually) says that when you pass a sNaN to an operation, it should "quiet" the sNaN or turn it into a qNaN, then set the invalid op exception.
You can see the exceptions in Rust, via asm, or in C:
src/lib.rs
Outdated
| } | ||
| } | ||
|
|
||
| fn sqrt(self) -> Self { |
There was a problem hiding this comment.
Currently:
/// Technically this should be StatusAnd<Self>, but since sqrt is exact for all supported formats,
/// we can just round to the nearest, ignore exceptions, and return Self.
The reasoning isn't correct: exact means correctly rounded, it doesn't mean no rounding will occur. You can have exact results with any of the four rounding modes. Instead it should just say that we don't support this.
Please also note that this is public documentation so the first line should just say what the function is before jumping into its details. Also please quote code in `...`.
|
Think all comments were handled, though I'm still not sure what to put for the documentation of the sqrt function itself |
|
So, turns out it's actually very easy to support status and rounding modes. Replace the final add+shift with the following: let mut status = Status::OK;
// A nonzero remainder indicates that we could continue processing sqrt if we had
// more precision, potentially indefinitely. We don't because we have enough bits
// to fill our significand already, and only need the one extra bit to determine
// rounding.
if rem != 0 {
status = Status::INEXACT;
match round {
// If the LSB is 0, we should round down and this 1 gets cut off. If the LSB
// is 1, it is either a tie (if all remaining bits would be 0) or something
// that should be rounded up.
//
// Square roots are either exact or irrational, so a `1` in the extra bit
// already implies an irrational result with more `1`s in the infinite
// precision tail that should be rounded up, which this does. We are in a
// `rem != 0` block but could technically add the `1` unconditionally, given
// that a 0 in the extra bit would imply an exact result to be rounded down
// (and the extra bit is just shifted out).
Round::NearestTiesToEven => res += 1,
// We know we have an inexact result that needs rounding up. If the round
// bit is 1, adding 1 is sufficient and adding 2 does nothing extra (the
// new LSB will get truncated). If the round bit is 0, we need to add
// two anyway to affect the significand.
Round::TowardPositive => res += 2,
// By default, shifting will round down.
Round::TowardNegative => (),
// Same as negative since the result of sqrt is positive.
Round::TowardZero => (),
Round::NearestTiesToAway => unimplemented!("unsupported rounding mode"),
};
}
// Remove the extra fractional bit.
res >>= 1;
// Build resulting value with res as mantissa and exp/2 as exponent
status.and(Self::from_u128(res).value.scalbn(exp / 2 - prec)) |
src/ieee.rs
Outdated
| // (Thanks @programmerjake for the comment) | ||
| Category::NaN => return IeeeDefaultExceptionHandling::result_from_nan(self).value, | ||
| // sqrt of negative number is NaN | ||
| _ if self.is_negative() => return Self::NAN, |
There was a problem hiding this comment.
Use NAN.copy_Sign like we do elsewhere so the NaN gets the sign bit set on negative inputs. IEEE doesn't require this but it's common.
src/ieee.rs
Outdated
| // However, Rust and LLVM allow input NaNs to be returned unmodified as well as a few other options -- see Rust's rules for NaNs. | ||
| // https://doc.rust-lang.org/std/primitive.f32.html#nan-bit-patterns | ||
| // (Thanks @programmerjake for the comment) | ||
| Category::NaN => return IeeeDefaultExceptionHandling::result_from_nan(self).value, |
There was a problem hiding this comment.
The two possible exceptions for sqrt are INEXACT on negative inputs and INVALID for sNaN inputs. My patch handles the INEXACT portion, you'll just need to drop .value here to handle INVALID.
There was a problem hiding this comment.
nope, it says to return INVALID on negative inputs. INEXACT would be returned for inputs where the rounded result is not the same as the infinitely precise result.
There was a problem hiding this comment.
Ah sorry, completely messed up the first bit of that comment. INEXACT if it requires rounding, INVALID for both negative inputs (as you said) and sNaN.
There was a problem hiding this comment.
Is something like this fine?
Category::NaN => return IeeeDefaultExceptionHandling::result_from_nan(self).value.copy_sign(Self::NAN),
|
Created a way to verify this on x86 hardware with a small bit of inline asm. Could you use this as the implementation to test against for the fuzzer? Please make sure to cfg correctly so you can still build on other arches, they can just do a I ran this addition with my above patch exhaustively for f32 on my machine and it works correctly in all four rounding modes. You'll need the NaN fix. |
|
Updated to include an aarch64 implementation as well https://gist.github.com/tgross35/7a3b35285497beeaacf3b699978e8cd2 |
tests/ieee.rs
Outdated
| #[test] | ||
| fn sqrt() { |
There was a problem hiding this comment.
Put this in tests/downstream.rs, which is where we have things that aren't a direct port of LLVM
| /// IEEE-754R sqrt: Returns the correctly rounded square root of the current value | ||
| /// Note: we currently don't support raising any exceptions from sqrt, so the result is always exact and the status is always OK. | ||
| fn sqrt(self) -> Self { | ||
| unimplemented!() |
There was a problem hiding this comment.
Please add a note like "sqrt is not implemented for these semantics"
src/ieee.rs
Outdated
| } | ||
| } | ||
|
|
||
| fn sqrt(self) -> Self { |
There was a problem hiding this comment.
Actually, similar to the above, could you put this function body into a new src/downstream.rs file? To avoid conflicts when we make changes related to LLVM. Call the function ieee_sqrt to be clear about what it supports.
|
@tgross35 where is the |
|
Look at the function signature for other unary functions in the |
|
I'm still not totally sure how the fuzzer works, and the test i copied was taking a REALLY long time to run, so I decided to just run it on the CI/CD and hope for the best |
|
I'm realizing the fuzzer doesn't actually run on CI so I'm in the process of making that happen, please bear with the conflicts that might pop up (I'll let you know when it's all set).
From the one I sent? Not sure exactly how long you mean but it's at least ~5 minutes to exhaustively check all possible |
| ); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Ah no I didn't mean to actually include this verbatim as a #[test], that's far too much to always run in CI. This implementation will need to be hooked up to the fuzzer, but hold off until I'm done with the changes there.
|
@tgross35 is the fuzzer stuff setup yet? |
|
Sorry, still working on some things and probably need another day or two here. I'll ping when it's ready. |
|
Okay, that took longer than expected. I wound up rewriting pretty much the whole fuzzer to support testing rounding modes and exceptions. Once you rebase the todo list should be basically:
I think you can then delete the additions to |
|
If #35 lands first then an x87_f80 implementation can be added as well, the op is Note that the fuzzer still isn't running in CI, there are a couple of failures going on still and I want to see if bumping to a newer LLVM version fixes them. But running locally for a while is fine. |
Please do add/keep edge case tests though. This should cover e.g. sNaN vs NaN, inputs that wind up with various flags, simple examples of rounding mode, zeros and infinities, etc. This can be based on some of the things covered in |
You forget that I can’t run the fuzzer locally since I’m on windows (though now that I think about it I guess I could try if WSL will work) Otherwise, sounds good, will get to it tomorrow |
|
Nope I remember, I meant I can run it for you if you can't get it set up :) But yeah WSL works, I've done some of the work there. |
|
@tgross35 Edit: Same issue with calling the fuzzer directly |
🙂 I changed it so you can download LLVM separately rather than having the build script download the huge file every time. |
yeah no that script doesn't work EDIT: Nevermind, I had to install the jq command line thing EDIT2: Okay after install jq, and then running the download script, I now get a different error: |
|
do you have |
Whoops, that should have been more clear but I typoed For the rest: it's not saying it can't find the download, it's saying it can't find the program it's trying to launch ( |


Had to remove the f16 and f128 stuff since its unstable technically
Code taken from:
https://github.com/rust-lang/miri/blob/3f739afcc08c8e81f97dbf0d698a940bab78e9bf/src/math.rs#L380-L460
https://github.com/rust-lang/miri/blob/3f739afcc08c8e81f97dbf0d698a940bab78e9bf/tests/pass/float.rs#L278-L305