Skip to content

Commit 5dddb9b

Browse files
rrbutanialexcrichton
authored andcommitted
Add some tests for pow
These probably aren't comprehensive but they cover all the edge cases identified in the original musl source.
1 parent 69ded67 commit 5dddb9b

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed

src/math/pow.rs

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,192 @@ pub fn pow(x: f64, y: f64) -> f64 {
408408

409409
return s * z;
410410
}
411+
412+
/// Special cases:
413+
414+
/// 20. (anything) ** 1 is (anything)
415+
/// 21. (anything) ** -1 is 1/(anything)
416+
/// 22. (-anything) ** (integer) is (-1)**(integer)*(+anything**integer)
417+
/// 23. (-anything except 0 and inf) ** (non-integer) is NAN
418+
419+
#[cfg(test)]
420+
mod tests {
421+
// #[macro_use]
422+
extern crate std;
423+
424+
use self::std::f64::consts::{E, PI};
425+
use self::std::f64::{EPSILON, INFINITY, MAX, MIN, MIN_POSITIVE, NAN, NEG_INFINITY};
426+
use super::pow;
427+
428+
// const TESTCASES: &[f64] = &[1.0, 0.0, PI, -PI, E, -E, MIN, MAX, MIN_POSITIVE, NAN, INFINITY, NEG_INFINITY];
429+
430+
const POS_ZERO: &[f64] = &[0.0];
431+
const NEG_ZERO: &[f64] = &[-0.0];
432+
const POS_ONE: &[f64] = &[1.0];
433+
const NEG_ONE: &[f64] = &[-1.0];
434+
const POS_FLOATS: &[f64] = &[E, PI, MAX];
435+
const NEG_FLOATS: &[f64] = &[-E, -PI, MIN];
436+
const POS_SMALL_FLOATS: &[f64] = &[(1.0 / 2.0), MIN_POSITIVE, EPSILON];
437+
const NEG_SMALL_FLOATS: &[f64] = &[-(1.0 / 2.0), -MIN_POSITIVE, -EPSILON];
438+
const POS_EVENS: &[f64] = &[2.0, 6.0, 8.0, 10.0, 22.0, 100.0];
439+
const NEG_EVENS: &[f64] = &[-8.0, -2.0];
440+
const POS_ODDS: &[f64] = &[3.0, 7.0];
441+
const NEG_ODDS: &[f64] = &[-7.0, -3.0];
442+
const NANS: &[f64] = &[NAN];
443+
// const EDGES: &[f64] = &[MIN, MAX, MIN_POSITIVE, EPSILON];
444+
const POS_INF: &[f64] = &[INFINITY];
445+
const NEG_INF: &[f64] = &[NEG_INFINITY];
446+
447+
const ALL: &[&[f64]] = &[
448+
POS_ZERO, NEG_ZERO, NANS, NEG_SMALL_FLOATS, POS_SMALL_FLOATS, NEG_FLOATS, POS_FLOATS, NEG_EVENS, POS_EVENS, NEG_ODDS, POS_ODDS,
449+
NEG_INF, POS_INF, NEG_ONE, POS_ONE,
450+
];
451+
const POS: &[&[f64]] = &[POS_ZERO, POS_ODDS, POS_ONE, POS_FLOATS, POS_EVENS, POS_INF];
452+
const NEG: &[&[f64]] = &[NEG_ZERO, NEG_ODDS, NEG_ONE, NEG_FLOATS, NEG_EVENS, NEG_INF];
453+
454+
fn pow_test(base: f64, exponent: f64, expected: f64) {
455+
let res = pow(base, exponent);
456+
assert!(if expected.is_nan() {res.is_nan()} else {pow(base, exponent) == expected},
457+
"{} ** {} was {} instead of {}", base, exponent, res, expected);
458+
}
459+
460+
fn test_sets_as_base(sets: &[&[f64]], exponent: f64, expected: f64) {
461+
sets.iter()
462+
.for_each(|s| s.iter().for_each(|val| pow_test(*val, exponent, expected)));
463+
}
464+
465+
fn test_sets_as_exponent(base: f64, sets: &[&[f64]], expected: f64) {
466+
sets.iter()
467+
.for_each(|s| s.iter().for_each(|val| pow_test(base, *val, expected)));
468+
}
469+
470+
fn test_sets(sets: &[&[f64]], computed: &Fn(f64) -> f64, expected: &Fn(f64) -> f64) {
471+
sets.iter()
472+
.for_each(|s| s.iter().for_each(|val| {
473+
let exp = expected(*val);
474+
let res = computed(*val);
475+
476+
assert!(if exp.is_nan() {res.is_nan()} else {exp == res},
477+
"test for {} was {} instead of {}", val, res, exp);
478+
}));
479+
}
480+
481+
/// 1. (anything) ** 0 is 1
482+
#[test]
483+
fn zero_as_exponent() {
484+
test_sets_as_base(ALL, 0.0, 1.0);
485+
test_sets_as_base(ALL, -0.0, 1.0);
486+
}
487+
488+
/// 2. 1 ** (anything) is 1
489+
#[test]
490+
fn one_as_base() {
491+
test_sets_as_exponent(1.0, ALL, 1.0);
492+
}
493+
494+
/// 3. (anything except 1) ** NAN is NAN
495+
/// 4. NAN ** (anything except 0) is NAN
496+
#[test]
497+
fn nan_inputs() {
498+
// NAN as the base:
499+
// (NAN ^ anything *but 0* should be NAN)
500+
test_sets_as_exponent(NAN, &ALL[2..], NAN);
501+
502+
// NAN as the exponent:
503+
// (anything *but 1* ^ NAN should be NAN)
504+
test_sets_as_base(&ALL[..(ALL.len() - 2)], NAN, NAN);
505+
}
506+
507+
/// 16. +INF ** (+anything except 0,NAN) is +INF
508+
/// 17. +INF ** (-anything except 0,NAN) is +0
509+
/// 18. -INF ** (+odd integer) is -INF
510+
/// 19. -INF ** (anything) = -0 ** (-anything), (anything except odd integer)
511+
#[test]
512+
fn infinity_as_base() {
513+
// Positive Infinity as the base:
514+
// (+Infinity ^ positive anything but 0 and NAN should be +Infinity)
515+
test_sets_as_exponent(INFINITY, &POS[1..], INFINITY);
516+
517+
// (+Infinity ^ negative anything except 0 and NAN should be 0.0)
518+
test_sets_as_exponent(INFINITY, &NEG[1..], 0.0);
519+
520+
// Negative Infinity as the base:
521+
// (-Infinity ^ positive odd ints should be -Infinity)
522+
test_sets_as_exponent(NEG_INFINITY, &[POS_ODDS], NEG_INFINITY);
523+
524+
// (-Infinity ^ anything but odd ints should be == -0 ^ (-anything))
525+
// We can lump in pos/neg odd ints here because they don't seem to
526+
// cause panics (div by zero) in release mode (I think).
527+
test_sets(ALL, &|v: f64| pow(NEG_INFINITY, v), &|v: f64| pow(-0.0, -v));
528+
}
529+
530+
/// 5. +-(|x| > 1) ** +INF is +INF
531+
/// 6. +-(|x| > 1) ** -INF is +0
532+
/// 7. +-(|x| < 1) ** +INF is +0
533+
/// 8. +-(|x| < 1) ** -INF is +INF
534+
/// 9. -1 ** +-INF is 1
535+
#[test]
536+
fn infinity_as_exponent() {
537+
// Positive/Negative base greater than 1:
538+
// (pos/neg > 1 ^ Infinity should be Infinity - note this excludes NAN as the base)
539+
test_sets_as_base(&ALL[5..(ALL.len() - 2)], INFINITY, INFINITY);
540+
541+
// (pos/neg > 1 ^ -Infinity should be 0.0)
542+
test_sets_as_base(&ALL[5..(ALL.len() - 2)], NEG_INFINITY, 0.0);
543+
544+
// Positive/Negative base less than 1:
545+
let base_below_one = &[POS_ZERO, NEG_ZERO, NEG_SMALL_FLOATS, POS_SMALL_FLOATS];
546+
547+
// (pos/neg < 1 ^ Infinity should be 0.0 - this also excludes NAN as the base)
548+
test_sets_as_base(base_below_one, INFINITY, 0.0);
549+
550+
// (pos/neg < 1 ^ -Infinity should be Infinity)
551+
test_sets_as_base(base_below_one, NEG_INFINITY, INFINITY);
552+
553+
// Positive/Negative 1 as the base:
554+
// (pos/neg 1 ^ Infinity should be 1)
555+
test_sets_as_base(&[NEG_ONE, POS_ONE], INFINITY, 1.0);
556+
557+
// (pos/neg 1 ^ -Infinity should be 1)
558+
test_sets_as_base(&[NEG_ONE, POS_ONE], NEG_INFINITY, 1.0);
559+
}
560+
561+
/// 10. +0 ** (+anything except 0, NAN) is +0
562+
/// 11. -0 ** (+anything except 0, NAN, odd integer) is +0
563+
/// 12. +0 ** (-anything except 0, NAN) is +INF, raise divbyzero
564+
/// 13. -0 ** (-anything except 0, NAN, odd integer) is +INF, raise divbyzero
565+
/// 14. -0 ** (+odd integer) is -0
566+
/// 15. -0 ** (-odd integer) is -INF, raise divbyzero
567+
#[test]
568+
fn zero_as_base() {
569+
// Positive Zero as the base:
570+
// (+0 ^ anything positive but 0 and NAN should be +0)
571+
test_sets_as_exponent(0.0, &POS[1..], 0.0);
572+
573+
// (+0 ^ anything negative but 0 and NAN should be Infinity)
574+
// (this should panic because we're dividing by zero but won't because release mode, I think)
575+
test_sets_as_exponent(0.0, &NEG[1..], INFINITY);
576+
577+
// Negative Zero as the base:
578+
// (-0 ^ anything positive but 0, NAN, and odd ints should be +0)
579+
test_sets_as_exponent(-0.0, &POS[3..], 0.0);
580+
581+
// (-0 ^ anything negative but 0, NAN, and odd ints should be Infinity)
582+
// (should panic because of divide by zero)
583+
test_sets_as_exponent(-0.0, &NEG[3..], INFINITY);
584+
585+
// (-0 ^ positive odd ints should be -0)
586+
test_sets_as_exponent(-0.0, &[POS_ODDS], -0.0);
587+
588+
// (-0 ^ negative odd ints should be -Infinity)
589+
// (should panic because of divide by zero)
590+
test_sets_as_exponent(-0.0, &[NEG_ODDS], NEG_INFINITY);
591+
}
592+
593+
#[test]
594+
fn normal_cases() {
595+
assert_eq!(pow(2.0, 20.0), (1 << 20) as f64);
596+
assert_eq!(pow(-1.0, 9.0), -1.0);
597+
assert!(pow(-1.0, 2.2).is_nan());
598+
}
599+
}

0 commit comments

Comments
 (0)