Repository: rust
Issue: premature underflow in division and powi on arm32 softfloat.
Labels: C-bug,A-floating-point

This bug started as a test failure of the num-rational crate on Debian armel https://github.com/rust-num/num-rational/issues/111 . Briefly the results from the num-rational crate was correct, but the result of 2f64.powi(MIN_EXP - 1) which was used as an "expected value in the test returned zero.

powi has pretty much no documentation as to what values it can be expected to work over, so I decided to write my own "naive" powi implementation. Unfortunately I had the same issue with that. After some further experimentation, I came to the conclusion that division was underflowing prematurely. To demonstrate this I wrote the following test code.

```rust
fn main() {
    println!("Normal   {:016x} {:?}",f64::MIN_POSITIVE.to_bits(),f64::MIN_POSITIVE);
    println!("Denormal {:016x} {:?}",1,f64::from_bits(1));

    let mut mnz:f64 = 0.0;
    let mut res:f64 = 1.0;
    let mut p = 0;
    while res > 0.0 {
       mnz = res;
       p -= 1;
       res = 2.0_f64.powi(p);
    }
    println!("powineg  {:016x} {:?}",mnz.to_bits(),mnz);

    let mut mnz:f64 = 0.0;
    let mut res:f64 = 1.0;
    let mut p = 0;
    while res > 0.0 {
       mnz = res;
       p += 1;
       res = 0.5_f64.powi(p);
    }
    println!("powipos  {:016x} {:?}",mnz.to_bits(),mnz);


    let mut mnz:f64 = 0.0;
    let mut res:f64 = 1.0;
    while res > 0.0 {
       mnz = res;
       res /= 2.0;
    }

    println!("divide   {:016x} {:?}",mnz.to_bits(),mnz);

    let mut mnz:f64 = 0.0;
    let mut res:f64 = 1.0;
    while res > 0.0 {
       mnz = res;
       res *= 0.5;
    }
    println!("multiply {:016x} {:?}",mnz.to_bits(),mnz);
}
```

This prints (in both hexadecimal bit pattern form and in standard floating point debug output form) the smallest positive normal and subnormal values, along with the smallest values that can be produced by.

1. Raising 2 to a large negative power using powi.
2. Raising 0.5 to a large positive power using powi
3. Starting with 1 and repeatedly dividing by 2.
4. Starting with 2 and repeatedly multiplying by 0.5.

On x86-64 and arm32 hardfloat I get the following.

    Normal   0010000000000000 2.2250738585072014e-308
    Denormal 0000000000000001 5e-324
    powineg  0008000000000000 1.1125369292536007e-308
    powipos  0000000000000001 5e-324
    divide   0000000000000001 5e-324
    multiply 0000000000000001 5e-324

This is unsurprising, multiplication, division and raising to a positive power all make it down to the smallest possible subnormal number. Raising to a negative power stops with a larger but still subnormal number, I suspect this is because it raises to a positive power first and then performs a reciprocal and the raising to a positive power overflows.

However when I run it on arm32 soft-float I get.

Normal   0010000000000000 2.2250738585072014e-308
Denormal 0000000000000001 5e-324
powineg  0020000000000000 4.450147717014403e-308
powipos  0000000000000001 5e-324
divide   0020000000000000 4.450147717014403e-308
multiply 0000000000000001 5e-324

The smallest number that can be reached by multiplication and by raising to a positive power is still the smallest possible subnormal value. However the smallest number that can be reached by division or raising to a negative power is much larger, it's twice the minimum possible normal value.

I have obvserved this failure with the following combinations of rustc version and triplet.

rustc 1.68.0-nightly (ad8ae0504 2022-12-29) arm-unknown-linux-gnueabi
rustc 1.63.0 (debian) armv5te-unknown-linux-gnueabi

I expected to see this happen: *explanation*

Instead, this happened: *explanation*

### Meta
<!--
If you're using the stable version of the compiler, you should also check if the
bug also exists in the beta or nightly versions.
-->

`rustc --version --verbose`:
```
<version>
```

<!--
Include a backtrace in the code block by setting `RUST_BACKTRACE=1` in your
environment. E.g. `RUST_BACKTRACE=1 cargo build`.
-->
<details><summary>Backtrace</summary>
<p>

```
<backtrace>
```

</p>
</details>