Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upto_degrees/to_radians aren't rounded correctly #29944
Comments
huonw
added
the
T-libs
label
Nov 20, 2015
This comment has been minimized.
This comment has been minimized.
|
This seems to not be super important based on:
Unless someone finds some really degenerate precision loss. While I agree that the perf hit isn't a big deal, I don't see a reason to take the hit unless someone's really hurting for the precision (and then they can of course roll their own in the worst-case). |
This comment has been minimized.
This comment has been minimized.
|
Although the vast majority of code I have contributed to Rust so far is dedicated to correct rounding of floats, I agree. If it's always within 1 ULP, then it's most likely perfectly okay. Theoretically some badly-written code might round-trip between degrees and radians n times, but that is a silly thing to do and dangerous anyway: You're bound to mess up with the units, unless you use a newtype wrapper, but a newtype wrapper can easily be augmented with a custom, correctly-rounded implementation of the conversions. |
This comment has been minimized.
This comment has been minimized.
Ok, I did an exhaustive test of all #![feature(float_extras)]
extern crate ieee754; // [dependencies] ieee754 = "0.2"
use ieee754::Ieee754;
use std::f32;
// C as f32
const C_H: f32 = 57.295780181884765625;
// (C - C_H) as f32
const C_L: f32 = -6.6880244276035227812826633453369140625e-7;
fn exact_mul(x: f32) -> f32 {
let a = C_L * x;
let b = C_H.mul_add(x, a);
b
}
fn main() {
let mut errs = vec![];
let mut count = 0_usize;
for x in f32::MIN.upto(f32::MAX) {
count += 1;
let exact = exact_mul(x);
let approx = x.to_degrees();
if exact != approx {
if let Some(ulp) = exact.ulp() {
let ulp_error = (exact - approx).abs() / ulp;
let bits_wrong = ulp_error.round() as u32;
errs.push(bits_wrong);
}
}
}
println!("{}/{} wrong, max ulp err {}",
errs.len(), count,
errs.iter().cloned().max().unwrap_or(0))
}
(Warning, this program requires ~11GB of RAM to finish.)
(I've now checked, and it does in single precision, and for all doubles except 8357367214274750 * 2n.) |
This comment has been minimized.
This comment has been minimized.
|
Thank you for testing this! 1 ULP vs 2 ULP does not make a big difference IMHO. Obviously using the correctly rounded constant in place of |
This comment has been minimized.
This comment has been minimized.
|
Yeah, I agree. (I believe there is infinite (absolute and relative) error for two (positive and negative) cases for |
This comment has been minimized.
This comment has been minimized.
|
Still repros. |
brson
added
P-low
I-wrong
labels
May 4, 2017
This comment has been minimized.
This comment has been minimized.
|
I suggest closing this as "wont fix", honestly. |
This comment has been minimized.
This comment has been minimized.
|
One thing someone could do with little effort is replacing |
huonw commentedNov 20, 2015
That computed answer is just one bit wrong.
The above case would be addressed by having the 180/pi value used in the conversion being a correctly rounded constant, rather than computed as
180.0 / consts::PI, although I'm not sure this will get the right answer in all cases. If we decide to care about this, then we'll need an exact multiplication algorithm such as Brisebarre and Muller (2008) Correctly rounded multiplication by arbitrary precision constants. LetCbe the exact (i.e. infinite precision) value of 180 / pi, then:(There's some conditions on the constant
Cfor this to work for allx, and I don't know/haven't yet checked if 180/pi satisfies them.)This will is noticably slower than the naive method, especially if there's not hardware support for FMA (
mul_add), however, if we do implement this, people who don't care about accuracy can use the naive method trivially. That said,to_degreesis almost always used for output for humans, and is often rounded to many fewer decimal places than full precision, so loss of precision doesn't matter... but also, speed probably doesn't matter so much (the formatting will almost certainly be much slower than the multiplication). On the other hand,to_radianswon't be used for human output, typically.(It's likely that
to_radianssuffers similarly, since pi / 180 is just as transcendental as 180 / pi, but I haven't found an example with a few tests.)This issue is somewhat of a policy issue: how much do we care about getting correctly rounded floating point answers? This case is pretty simple, but we almost certainly don't want to guarantee 100% precise answers for functions like
sin/expetc., since this is non-trivial/slow to do, and IEEE754 (and typical libm's) don't guarantee exact rounding for all inputs.