Strange floating-point results with transcendental functions

All the functions and their inverses below work, with the exception of: sine and cosine. The hyperbolic versions of each work, but the base functions do not. The assertions fail when: the random value which determines which function to use happens to choose sine or cosine, then the inverse does not map correctly to the original input:

/// applies a pseudo-random function to the input. This is used to produce a secure value which identifies a packet.
///
/// It is close to necessary for the drill to be used in order to find the correct values, since `op_value` comes from
/// the drill. The produced floating point value must be correct forall digits
///
/// Produces a floating-point number. All functions must necessarily be surjective
#[inline]
fn get_op_result_by_input(op_value: u8, coefficient: u32, input: u32) -> f64 {
    println!("op_value: {}", op_value);
    println!("{} * op({})", coefficient, input);
    let input = input as f64;
    match op_value {
        0..32 => {
            coefficient as f64 * (input).ln()
        },

        32..64 => {
            coefficient as f64 * (input).sin()
        },

        64..96 => {
            coefficient as f64 * (input).cos()
        },

        96..128 => {
            coefficient as f64 * (input).tan()
        },

        128..160 => {
            coefficient as f64 * (input).sinh()
        },

        160..192 => {
            coefficient as f64 * (input).cosh()
        },

        192..224 => {
            coefficient as f64 * (input).tanh()
        },

        224..=255 => {
            coefficient as f64 * (input).recip()
        }
    }
}

#[inline]
fn get_op_result_by_input_inv(op_value: u8, coefficient: u32, input: f64) -> usize {
    println!("op_value: {}", op_value);
    let ka = input / f64::from(coefficient);
    println!("{} / {} = ka == {}", coefficient, input, ka);
    match op_value {
        // natural log
        0..32 => {
            ka.exp().round() as usize
        },

        32..64 => {
            ka.asin().round() as usize
        },

        64..96 => {
            ka.acos().round() as usize
        },

        96..128 => {
            ka.atan().round() as usize
        },

        128..160 => {
            ka.asinh().round() as usize
        },

        160..192 => {
            ka.acosh().round() as usize
        },

        192..224 => {
            ka.atanh().round() as usize
        },

        224..=255 => {
            ka.recip().round() as usize
        }
    }
}

/// `x` should be the packet number in the wave
pub fn calculate_pid<Drx: DrillType>(x: usize, drill: &Drx) -> f64 {
    get_op_result_by_input(drill.get_low()[VIRTUAL_TIME_INDEX][0], drill.get_high()[VIRTUAL_TIME_INDEX][0], x as u32)
}

#[inline]
/// The inverse function of `calculate_pid`
pub fn calculate_pid_inv<Drx: DrillType>(y: f64, drill: &Drx) -> usize {
    get_op_result_by_input_inv(drill.get_low()[VIRTUAL_TIME_INDEX][0], drill.get_high()[VIRTUAL_TIME_INDEX][0], y)
}

/// `x` should be the wave number in the wave series
pub fn calculate_wid<Drx: DrillType>(x: usize, drill: &Drx) -> f64 {
    get_op_result_by_input(drill.get_low()[VIRTUAL_TIME_INDEX][1], drill.get_high()[VIRTUAL_TIME_INDEX][1], x as u32)
}

#[inline]
/// The inverse function of `calculate_wid`
pub fn calculate_wid_inv<Drx: DrillType>(y: f64, drill: &Drx) -> usize {
    get_op_result_by_input_inv(drill.get_low()[VIRTUAL_TIME_INDEX][1], drill.get_high()[VIRTUAL_TIME_INDEX][1], y)
}

And (part) of the unit test:

            let input = 10;
            let pid = calculate_pid(input, drill.deref());
            println!("-> {}", pid);
            let pid_inv = calculate_pid_inv(pid, drill.deref());
            println!("-> {}", pid_inv);
            assert_eq!(input, pid_inv);
            Ok(())

What's an example of an input value that fails your check? Sine and cosine are periodic functions, and arcsin(sin(x)) == x will only hold in part of the domain

2 Likes

Oh, that mathematical discrepancy may be why then. So, sine and sine inverse aren't necessarily surjective? I thought the sine inverse of sin(x) returns x for all x in sine's domain.

You can restrict your definition of sine to sin x where x in -π/2 <= x <= π/2 and then you have a function which has an actual inverse - arcsin - because this sin x has a one-to-one correspondance between input and output points (a bijection).

3 Likes