Skip to content

float tests fails on wasm32-unknown-emscripten #42630

Closed
@malbarbo

Description

@malbarbo

This tests (taken from rust tests):

#![feature(dec2flt)]

#![allow(unused_macros, unused_imports, dead_code)]

extern crate core;

use std::num::FpCategory::*;
use std::num::FpCategory as Fp;
use core::num::dec2flt::rawfp::{RawFloat, Unpacked};

const SOME_FLOATS: [f64; 9] =
    [0.1f64, 33.568, 42.1e-5, 777.0e9, 1.1111, 0.347997,
     9843579834.35892, 12456.0e-150, 54389573.0e-150];

// src/libcore/tests/num/dec2flt/mod.rs

// Take a float literal, turn it into a string in various ways (that are all trusted
// to be correct) and see if those strings are parsed back to the value of the literal.
// Requires a *polymorphic literal*, i.e. one that can serve as f64 as well as f32.
macro_rules! test_literal {
    ($x: expr) => ({
        let x32: f32 = $x;
        let x64: f64 = $x;
        let inputs = &[stringify!($x).into(), format!("{:?}", x64), format!("{:e}", x64)];
        for input in inputs {
            assert_eq!(input.parse(), Ok(x64));
            assert_eq!(input.parse(), Ok(x32));
            let neg_input = &format!("-{}", input);
            assert_eq!(neg_input.parse(), Ok(-x64));
            assert_eq!(neg_input.parse(), Ok(-x32));
        }
    })
}

#[test]
fn ordinary() {
    test_literal!(1.0);
    test_literal!(3e-5);
    test_literal!(0.1);
    test_literal!(12345.);
    test_literal!(0.9999999);
    test_literal!(2.2250738585072014e-308);
}

#[test]
fn special_code_paths() {
    test_literal!(36893488147419103229.0); // 2^65 - 3, triggers half-to-even with even significand
    test_literal!(101e-33); // Triggers the tricky underflow case in AlgorithmM (for f32)
    test_literal!(1e23); // Triggers AlgorithmR
    test_literal!(2075e23); // Triggers another path through AlgorithmR
    test_literal!(8713e-23); // ... and yet another.
}


// src/libcore/tests/num/dec2flt/rawfp.rs

#[test]
fn prev_float_monotonic() {
    let mut x = 1.0;
    for _ in 0..100 {
        let x1 = prev_float(x);
        assert!(x1 < x);
        assert!(x - x1 < 1e-15);
        x = x1;
    }
}

#[test]
fn next_prev_identity() {
    for &x in &SOME_FLOATS {
        assert_eq!(prev_float(next_float(x)), x);
        assert_eq!(prev_float(prev_float(next_float(next_float(x)))), x);
        assert_eq!(next_float(prev_float(x)), x);
        assert_eq!(next_float(next_float(prev_float(prev_float(x)))), x);
    }
}


/// Inverse of `RawFloat::unpack()` for normalized numbers.
/// Panics if the significand or exponent are not valid for normalized numbers.
pub fn encode_normal<T: RawFloat>(x: Unpacked) -> T {
    debug_assert!(T::MIN_SIG <= x.sig && x.sig <= T::MAX_SIG,
        "encode_normal: significand not normalized");
    // Remove the hidden bit
    let sig_enc = x.sig & !(1 << T::EXPLICIT_SIG_BITS);
    // Adjust the exponent for exponent bias and mantissa shift
    let k_enc = x.k + T::MAX_EXP + T::EXPLICIT_SIG_BITS as i16;
    debug_assert!(k_enc != 0 && k_enc < T::MAX_ENCODED_EXP,
        "encode_normal: exponent out of range");
    // Leave sign bit at 0 ("+"), our numbers are all positive
    let bits = (k_enc as u64) << T::EXPLICIT_SIG_BITS | sig_enc;
    T::from_bits(bits)
}

/// Find the largest floating point number strictly smaller than the argument.
/// Does not handle subnormals, zero, or exponent underflow.
pub fn prev_float<T: RawFloat>(x: T) -> T {
    match x.classify() {
        Infinite => panic!("prev_float: argument is infinite"),
        Nan => panic!("prev_float: argument is NaN"),
        Subnormal => panic!("prev_float: argument is subnormal"),
        Zero => panic!("prev_float: argument is zero"),
        Normal => {
            let Unpacked { sig, k } = x.unpack();
            if sig == T::MIN_SIG {
                encode_normal(Unpacked::new(T::MAX_SIG, k - 1))
            } else {
                encode_normal(Unpacked::new(sig - 1, k))
            }
        }
    }
}

// Find the smallest floating point number strictly larger than the argument.
// This operation is saturating, i.e. next_float(inf) == inf.
// Unlike most code in this module, this function does handle zero, subnormals, and infinities.
// However, like all other code here, it does not deal with NaN and negative numbers.
pub fn next_float<T: RawFloat>(x: T) -> T {
    match x.classify() {
        Nan => panic!("next_float: argument is NaN"),
        Infinite => T::INFINITY,
        // This seems too good to be true, but it works.
        // 0.0 is encoded as the all-zero word. Subnormals are 0x000m...m where m is the mantissa.
        // In particular, the smallest subnormal is 0x0...01 and the largest is 0x000F...F.
        // The smallest normal number is 0x0010...0, so this corner case works as well.
        // If the increment overflows the mantissa, the carry bit increments the exponent as we
        // want, and the mantissa bits become zero. Because of the hidden bit convention, this
        // too is exactly what we want!
        // Finally, f64::MAX + 1 = 7eff...f + 1 = 7ff0...0 = f64::INFINITY.
        Zero | Subnormal | Normal => {
            let bits: u64 = x.transmute();
            T::from_bits(bits + 1)
        }
    }
}


// src/libcore/tests/num/mod.rs
#[test]
fn test_f32f64() {
    use core::f32;

    let max: f64 = f32::MAX.into();
    assert_eq!(max as f32, f32::MAX);
    assert!(max.is_normal());

    let min: f64 = f32::MIN.into();
    assert_eq!(min as f32, f32::MIN);
    assert!(min.is_normal());

    let min_positive: f64 = f32::MIN_POSITIVE.into();
    assert_eq!(min_positive as f32, f32::MIN_POSITIVE);
    assert!(min_positive.is_normal());

    let epsilon: f64 = f32::EPSILON.into();
    assert_eq!(epsilon as f32, f32::EPSILON);
    assert!(epsilon.is_normal());

    let zero: f64 = (0.0f32).into();
    assert_eq!(zero as f32, 0.0f32);
    assert!(zero.is_sign_positive());

    let neg_zero: f64 = (-0.0f32).into();
    assert_eq!(neg_zero as f32, -0.0f32);
    assert!(neg_zero.is_sign_negative());

    let infinity: f64 = f32::INFINITY.into();
    assert_eq!(infinity as f32, f32::INFINITY);
    assert!(infinity.is_infinite());
    assert!(infinity.is_sign_positive());

    let neg_infinity: f64 = f32::NEG_INFINITY.into();
    assert_eq!(neg_infinity as f32, f32::NEG_INFINITY);
    assert!(neg_infinity.is_infinite());
    assert!(neg_infinity.is_sign_negative());

    let nan: f64 = f32::NAN.into();
    assert!(nan.is_nan());
}


// src/libstd/f64.rs
#[test]
fn test_one() {
    let one: f64 = 1.0f64;
    assert_eq!(1.0, one);
    assert!(!one.is_infinite());
    assert!(one.is_finite());
    assert!(one.is_sign_positive());
    assert!(!one.is_sign_negative());
    assert!(!one.is_nan());
    assert!(one.is_normal());
    assert_eq!(Fp::Normal, one.classify());
}

#[test]
fn test_is_normal() {
    use std::f64::*;
    let nan: f64 = NAN;
    let inf: f64 = INFINITY;
    let neg_inf: f64 = NEG_INFINITY;
    let zero: f64 = 0.0f64;
    let neg_zero: f64 = -0.0;
    assert!(!nan.is_normal());
    assert!(!inf.is_normal());
    assert!(!neg_inf.is_normal());
    assert!(!zero.is_normal());
    assert!(!neg_zero.is_normal());
    assert!(1f64.is_normal());
    assert!(1e-307f64.is_normal());
    assert!(!1e-308f64.is_normal());
}

#[test]
fn test_classify() {
    use std::f64::*;
    let nan: f64 = NAN;
    let inf: f64 = INFINITY;
    let neg_inf: f64 = NEG_INFINITY;
    let zero: f64 = 0.0f64;
    let neg_zero: f64 = -0.0;
    assert_eq!(nan.classify(), Fp::Nan);
    assert_eq!(inf.classify(), Fp::Infinite);
    assert_eq!(neg_inf.classify(), Fp::Infinite);
    assert_eq!(zero.classify(), Fp::Zero);
    assert_eq!(neg_zero.classify(), Fp::Zero);
    assert_eq!(1e-307f64.classify(), Fp::Normal);
    assert_eq!(1e-308f64.classify(), Fp::Subnormal);
}

fails on wasm32-unknown-emscripten. I think that this as bug in asm2wasm because the same tests works on asmjs-unknown-emscripten. I used cross to run the tests (it is necessary to create a project and put the code in lib.rs):

cross test --target asmjs-unknown-emscripten # works
cross test --target wasm32-unknown-emscripten # fails

Tests results:

running 8 tests
test next_prev_identity ... FAILED
test ordinary ... FAILED
test prev_float_monotonic ... FAILED
test special_code_paths ... FAILED
test test_classify ... FAILED
test test_f32f64 ... FAILED
test test_is_normal ... FAILED
test test_one ... FAILED

failures:

---- next_prev_identity stdout ----
	thread 'main' panicked at 'prev_float: argument is subnormal', src/lib.rs:101
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- ordinary stdout ----
	thread 'main' panicked at 'assertion failed: `(left == right)` (left: `Ok(0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201)`, right: `Ok(0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201)`)', src/lib.rs:42

---- prev_float_monotonic stdout ----
	thread 'main' panicked at 'prev_float: argument is subnormal', src/lib.rs:101

---- special_code_paths stdout ----
	thread 'main' panicked at 'assertion failed: `(left == right)` (left: `Ok(36893488147419100000)`, right: `Ok(36893488147419100000)`)', src/lib.rs:47

---- test_classify stdout ----
	thread 'main' panicked at 'assertion failed: `(left == right)` (left: `Subnormal`, right: `Normal`)', src/lib.rs:227

---- test_f32f64 stdout ----
	thread 'main' panicked at 'assertion failed: max.is_normal()', src/lib.rs:145

---- test_is_normal stdout ----
	thread 'main' panicked at 'assertion failed: 1f64.is_normal()', src/lib.rs:209

---- test_one stdout ----
	thread 'main' panicked at 'assertion failed: one.is_normal()', src/lib.rs:192


failures:
    next_prev_identity
    ordinary
    prev_float_monotonic
    special_code_paths
    test_classify
    test_f32f64
    test_is_normal
    test_one

test result: FAILED. 0 passed; 8 failed; 0 ignored; 0 measured; 0 filtered out

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-testsuiteArea: The testsuite used to check the correctness of rustcC-bugCategory: This is a bug.O-wasmTarget: WASM (WebAssembly), http://webassembly.org/

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions