Skip to content

Commit 81e7c06

Browse files
committed
fix(double): op_mod for subnormal double (moonbitlang#1303)
1 parent 3de9e44 commit 81e7c06

File tree

3 files changed

+90
-60
lines changed

3 files changed

+90
-60
lines changed

NOTICE

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
7171
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
7272
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
7373

74-
File `float/exp.mbt`, `float/log.mbt`, and `double/scalbn.mbt` is adapted from
74+
File `float/exp.mbt`, `float/log.mbt`, `double/mod_nonjs.mbt` and `double/scalbn.mbt` is adapted from
7575
[musl](https://www.musl-libc.org/). Specifically:
7676

7777
- `float/log.mbt` is adapted from `src/math/logf.c`, `src/math/logf_data.h`,
7878
and `src/math/logf_data.c`.
7979
- `float/exp.mbt` is adapted from `src/math/expf.c`, `src/math/exp2f_data.h`,
8080
and `src/math/exp2f_data.c`.
81+
- `double/mod_nonjs.mbt` is adapted from `src/math/fmod.c`.
8182
- `double/scalbn.mbt` is adapted from `src/math/scalbn.c`.
8283

8384
`float/log.mbt` and `float/exp.mbt` files are Copyright (c) 2017-2018 Arm Limited and licensed under the MIT
@@ -104,7 +105,7 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
104105
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
105106
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
106107

107-
`double/scalbn.mbt` is licensed under the following license:
108+
`double/mod_nonjs.mbt` and `double/scalbn.mbt` is licensed under the following license:
108109

109110
Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
110111

double/mod_nonjs.mbt

Lines changed: 66 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,69 +13,80 @@
1313
// limitations under the License.
1414

1515
///|
16-
fn copy_sign(x : Double, y : Double) -> Double {
17-
if x < 0 {
18-
-y.abs()
19-
} else {
20-
y.abs()
16+
pub fn op_mod(self : Double, other : Double) -> Double {
17+
let x = self
18+
let y = other
19+
let mut uint64_x = x.reinterpret_as_uint64()
20+
let mut uint64_y = y.reinterpret_as_uint64()
21+
let mut ex = ((uint64_x >> 52) & 0x7FF).to_int()
22+
let mut ey = ((uint64_y >> 52) & 0x7FF).to_int()
23+
let sign_x = uint64_x >> 63
24+
let mut i : UInt64 = 0
25+
if (uint64_y << 1) == 0 || y.is_nan() || ex == 0x7ff {
26+
return x * y / (x * y)
2127
}
22-
}
23-
24-
///|
25-
fn ldexp(frac : Double, exp_ : Double) -> Double {
26-
if frac == 0 {
27-
0
28-
} else if frac.is_inf() || frac.is_nan() {
29-
frac
30-
} else {
31-
let (frac, e) = normalize(frac)
32-
let mut exp = exp_
33-
exp += e.to_double()
34-
let mut x = frac.reinterpret_as_uint64()
35-
exp += (((x >> 52).to_int() & 0x7FF) - 1023).to_double()
36-
if exp < -1075 {
37-
copy_sign(0, frac)
38-
} else if exp > 1023 {
39-
if frac < 0 {
40-
neg_infinity
41-
} else {
42-
infinity
43-
}
44-
} else {
45-
let mut m : Double = 1
46-
if exp < -1022 {
47-
exp += 53
48-
m = 1.0 / (1 << 53).to_double()
49-
}
50-
x = x & (0x7FFUL << 52).lnot()
51-
x = x | ((exp + 1023.0).to_int64().reinterpret_as_uint64() << 52)
52-
m * x.reinterpret_as_double()
28+
if (uint64_x << 1) <= (uint64_y << 1) {
29+
if (uint64_x << 1) == (uint64_y << 1) {
30+
return 0.0 * x
5331
}
32+
return x
5433
}
55-
}
5634

57-
///|
58-
pub fn op_mod(self : Double, other : Double) -> Double {
59-
if other == 0 || self.is_inf() || self.is_nan() || other.is_nan() {
60-
not_a_number
35+
// normalize x and y
36+
if ex == 0 {
37+
i = uint64_x << 12
38+
while (i >> 63) == 0 {
39+
ex -= 1
40+
i = i << 1
41+
}
42+
uint64_x = uint64_x << (-ex + 1)
6143
} else {
62-
let y = other.abs()
63-
let (yfr, yexp) = frexp(y)
64-
let mut r = self
65-
if self < 0 {
66-
r = -self
44+
uint64_x = uint64_x & (18446744073709551615UL >> 12)
45+
uint64_x = uint64_x | (1UL << 52)
46+
}
47+
if ey == 0 {
48+
i = uint64_y << 12
49+
while (i >> 63) == 0 {
50+
ey -= 1
51+
i = i << 1
6752
}
68-
while r >= y {
69-
let (rfr, rexp_) = frexp(r)
70-
let mut rexp = rexp_
71-
if rfr < yfr {
72-
rexp -= 1
53+
uint64_y = uint64_y << (-ey + 1)
54+
} else {
55+
uint64_y = uint64_y & (18446744073709551615UL >> 12)
56+
uint64_y = uint64_y | (1UL << 52)
57+
}
58+
59+
// x mod y
60+
while ex > ey {
61+
i = uint64_x - uint64_y
62+
if (i >> 63) == 0 {
63+
if i == 0 {
64+
return 0.0 * x
7365
}
74-
r = r - ldexp(y, (rexp - yexp).to_double())
66+
uint64_x = i
7567
}
76-
if self < 0 {
77-
r = -r
68+
uint64_x = uint64_x << 1
69+
ex -= 1
70+
}
71+
i = uint64_x - uint64_y
72+
if (i >> 63) == 0 {
73+
if i == 0 {
74+
return 0.0 * x
7875
}
79-
r
76+
uint64_x = i
77+
}
78+
while (uint64_x >> 52) == 0 {
79+
uint64_x = uint64_x << 1
80+
ex -= 1
81+
}
82+
83+
// scale result
84+
if ex > 0 {
85+
uint64_x = uint64_x - (1UL << 52)
86+
uint64_x = uint64_x | (ex.to_uint64() << 52)
87+
} else {
88+
uint64_x = uint64_x >> (-ex + 1)
8089
}
90+
uint64_x = uint64_x | (sign_x << 63)
91+
uint64_x.reinterpret_as_double()
8192
}

double/mod_test.mbt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,33 @@
1313
// limitations under the License.
1414

1515
///|
16+
/// Check if two floating point numbers are equal within a small epsilon.
17+
///
18+
/// Only used for testing when the result can't be precisely represented
19+
/// by IEEE 754 double.
1620
fn check(x : Double, y : Double) -> Bool {
1721
(x - y).abs() < 1.0e-15
1822
}
1923

2024
test "mod" {
25+
// normal numbers
2126
inspect!(check(7.5 % 2.3, 0.6), content="true")
22-
inspect!(check(5.75 % 5.75, 0), content="true")
2327
inspect!(check(-3.6 % 1.4, -0.8), content="true")
24-
inspect!(check(15.25 % 4.5, 1.75), content="true")
2528
inspect!(check(0.7 % 0.2, 0.1), content="true")
26-
inspect!(check(-8.4 % 3.2, -2), content="true")
29+
inspect!(5.75 % 5.75, content="0")
30+
inspect!(15.25 % 4.5, content="1.75")
31+
inspect!(-8.4 % 3.2, content="-2")
32+
// subnormal numbers
33+
inspect!(5.0e-324 % 5.0e-324, content="0")
34+
inspect!(0.5 % 1.5e-323, content="1e-323")
35+
// inf
36+
inspect!(1.0 % @double.infinity, content="1")
37+
assert_true!(@double.is_nan(@double.infinity % @double.infinity))
38+
assert_true!(@double.is_nan(@double.infinity % 1))
39+
// division by zero
40+
assert_true!(@double.is_nan(1.0 % 0))
41+
assert_true!(@double.is_nan(1.0 % -0))
42+
// nan
43+
assert_true!(@double.is_nan(@double.not_a_number % 1.0))
44+
assert_true!(@double.is_nan(1.0 % @double.not_a_number))
2745
}

0 commit comments

Comments
 (0)