SIGN IN SIGN UP

Fix too restrictive optimization in Java RyuFloat impl. (#244)

When `e2 < 0`, current `RyuFloat`  calculates `dvIsTrailingZeros` by the code below, which should be equivalent to `mv%2^(q-1) == 0`.

```java
dvIsTrailingZeros = (q < FLOAT_MANTISSA_BITS) && (mv & ((1 << (q - 1)) - 1)) == 0;
```

https://github.com/ulfjack/ryu/blob/1264a946ba66eab320e927bfd2362e0c8580c42f/src/main/java/info/adams/ryu/RyuFloat.java#L207

If I understand correctly, `(q < FLOAT_MANTISSA_BITS)` is an optimization that works as follows:
- The maximum value of `v` is `2^FLOAT_MANTISSA_BITS - 1`
- Therefore, when `q` is greater than or equal to `FLOAT_MANTISSA_BITS`, `v` is never divisible by `2^q`
- So, if `(q < FLOAT_MANTISSA_BITS)` is false, we can skip the `(mv & ((1 << (q - 1)) - 1)) == 0` check

**However, I think it should be `(q - 1 < FLOAT_MANTISSA_BITS + 2)` because**:
- We're checking if `mv` is divisible by `2^(q-1)`, not `2^q`. So the left-hand side should be `q - 1`
- The maximum value of `mv` is larger than `2^FLOAT_MANTISSA_BITS - 1`:
  - `mv` is `4 * m`, where `m` is the 23-bit mantissa. This means `mv <= 4 * (2^23 - 1) = 2^25 - 4`
  - To check if `mv` is divisible by `2^(q-1)`, the condition `q < 23` is too restrictive, because `mv` can be divisible by up to `2^24`
  - Therefore, the right-hand side should be `FLOAT_MANTISSA_BITS + 2`

---

Actually, a test from `f2s_test.cc`'s `lotsOfTrailingZeros` fails in the Java implementation:
https://github.com/ulfjack/ryu/blob/1264a946ba66eab320e927bfd2362e0c8580c42f/ryu/tests/f2s_test.cc#L69
```
1) lotsOfTrailingZeros(info.adams.ryu.RyuFloatTest)
org.junit.ComparisonFailure: expected:<0.002441406[2]> but was:<0.002441406[3]>
```
This is because:
- `mv = 41943040`, `e2 = -34`, and `q = 23`
- Therefore, `dvIsTrailingZeros = false` because `q < 23` is false (but it should be true)
- By step 4, `dv` is reduced from `244140625` to `24414062` (with `lastRemovedDigit = 5`)
- The condition `(dvIsTrailingZeros && (lastRemovedDigit == 5) && (dv % 2 == 0))` evaluates to false (but it should be true!), because `dvIsTrailingZeros = false`
https://github.com/ulfjack/ryu/blob/1264a946ba66eab320e927bfd2362e0c8580c42f/src/main/java/info/adams/ryu/RyuFloat.java#L267-L269
As a result, it is rounded up to `24414063`, but it should be rounded to `24414062`.
R
Rikito Taniguchi committed
b977d8022ce3f3e4ab6e73cf026825df68676d61
Parent: 3377662
Committed by GitHub <noreply@github.com> on 1/16/2026, 5:25:03 PM