Skip to content

Commit 04de8f1

Browse files
committed
avm2: Implement Math methods in Number
1 parent ba02cfd commit 04de8f1

File tree

20 files changed

+195
-26
lines changed

20 files changed

+195
-26
lines changed

core/src/avm2/globals/Number.as

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,82 @@ package {
3636
[API("680")]
3737
public static const LOG10E:Number = 0.4342944819032518;
3838

39+
// This is a hacky way to specify `-Infinity` as a default value.
40+
private static const NegInfinity: Number = -1 / 0;
41+
private static const PosInfinity: Number = 1 / 0;
42+
43+
[API("680")]
44+
[Ruffle(FastCall)]
45+
public static native function abs(x:Number):Number;
46+
47+
[API("680")]
48+
[Ruffle(FastCall)]
49+
public static native function acos(x:Number):Number;
50+
51+
[API("680")]
52+
[Ruffle(FastCall)]
53+
public static native function asin(x:Number):Number;
54+
55+
[API("680")]
56+
[Ruffle(FastCall)]
57+
public static native function atan(x:Number):Number;
58+
59+
[API("680")]
60+
[Ruffle(FastCall)]
61+
public static native function atan2(y:Number, x:Number):Number;
62+
63+
[API("680")]
64+
[Ruffle(FastCall)]
65+
public static native function ceil(x:Number):Number;
66+
67+
[API("680")]
68+
[Ruffle(FastCall)]
69+
public static native function cos(x:Number):Number;
70+
71+
[API("680")]
72+
[Ruffle(FastCall)]
73+
public static native function exp(x:Number):Number;
74+
75+
[API("680")]
76+
[Ruffle(FastCall)]
77+
public static native function floor(x:Number):Number;
78+
79+
[API("680")]
80+
[Ruffle(FastCall)]
81+
public static native function log(x:Number):Number;
82+
83+
[API("680")]
84+
[Ruffle(FastCall)]
85+
public static native function max(x:Number = NegInfinity, y:Number = NegInfinity, ...rest):Number;
86+
87+
[API("680")]
88+
[Ruffle(FastCall)]
89+
public static native function min(x:Number = PosInfinity, y:Number = PosInfinity, ...rest):Number;
90+
91+
[API("680")]
92+
[Ruffle(FastCall)]
93+
public static native function pow(x:Number, y:Number):Number;
94+
95+
[API("680")]
96+
[Ruffle(FastCall)]
97+
public static native function random():Number;
98+
99+
[API("680")]
100+
[Ruffle(FastCall)]
101+
public static native function round(x:Number):Number;
102+
103+
[API("680")]
104+
[Ruffle(FastCall)]
105+
public static native function sin(x:Number):Number;
106+
107+
[API("680")]
108+
[Ruffle(FastCall)]
109+
public static native function sqrt(x:Number):Number;
110+
111+
[API("680")]
112+
[Ruffle(FastCall)]
113+
public static native function tan(x:Number):Number;
114+
39115
{
40116
prototype.toExponential = function(digits:* = 0):String {
41117
var self:Number = this;

core/src/avm2/globals/math.rs

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::avm2::error::type_error;
55
use crate::avm2::object::Object;
66
use crate::avm2::value::Value;
77
use crate::avm2::{ClassObject, Error};
8+
use num_traits::ToPrimitive;
89
use rand::Rng;
910

1011
macro_rules! wrap_std {
@@ -20,14 +21,11 @@ macro_rules! wrap_std {
2021
};
2122
}
2223

23-
wrap_std!(abs, f64::abs);
2424
wrap_std!(acos, f64::acos);
2525
wrap_std!(asin, f64::asin);
2626
wrap_std!(atan, f64::atan);
27-
wrap_std!(ceil, f64::ceil);
2827
wrap_std!(cos, f64::cos);
2928
wrap_std!(exp, f64::exp);
30-
wrap_std!(floor, f64::floor);
3129
wrap_std!(log, f64::ln);
3230
wrap_std!(sin, f64::sin);
3331
wrap_std!(sqrt, f64::sqrt);
@@ -66,7 +64,10 @@ pub fn round<'gc>(
6664
// Note that Flash Math.round always rounds toward infinity,
6765
// unlike Rust f32::round which rounds away from zero.
6866
let ret = (x + 0.5).floor();
69-
Ok(ret.into())
67+
match ret.to_i32() {
68+
Some(num) => Ok(Value::Integer(num)),
69+
None => Ok(Value::Number(ret)),
70+
}
7071
}
7172

7273
pub fn atan2<'gc>(
@@ -90,7 +91,9 @@ pub fn max<'gc>(
9091
let val = arg.coerce_to_number(activation)?;
9192
if val.is_nan() {
9293
return Ok(f64::NAN.into());
93-
} else if val > cur_max {
94+
} else if val > cur_max
95+
|| (val == cur_max && !val.is_sign_negative() && cur_max.is_sign_negative())
96+
{
9497
cur_max = val;
9598
};
9699
}
@@ -107,20 +110,44 @@ pub fn min<'gc>(
107110
let val = arg.coerce_to_number(activation)?;
108111
if val.is_nan() {
109112
return Ok(f64::NAN.into());
110-
} else if val < cur_min {
113+
} else if val < cur_min
114+
|| (val == cur_min && val.is_sign_negative() && !cur_min.is_sign_negative())
115+
{
111116
cur_min = val;
112117
}
113118
}
114119
Ok(cur_min.into())
115120
}
116121

117122
pub fn pow<'gc>(
118-
_activation: &mut Activation<'_, 'gc>,
123+
activation: &mut Activation<'_, 'gc>,
119124
_this: Value<'gc>,
120125
args: &[Value<'gc>],
121126
) -> Result<Value<'gc>, Error<'gc>> {
122-
let n = args[0].as_f64();
123-
let p = args[1].as_f64();
127+
let n = args[0].coerce_to_number(activation)?;
128+
let p = args[1].coerce_to_number(activation)?;
129+
130+
// If y is NaN, the result is NaN.
131+
if p.is_nan() {
132+
return Ok(f64::NAN.into());
133+
}
134+
135+
// Special case: If x is ±1 and y is ±Infinity, the result is NaN.
136+
if (n == 1.0 || n == -1.0) && p.is_infinite() {
137+
return Ok(f64::NAN.into());
138+
}
139+
140+
// Special case: If x is -Infinity and y<0 and y is an even integer, Flash Player returns -0.
141+
if n == f64::NEG_INFINITY && p < 0.0 && p.is_finite() {
142+
// Check if p is an integer
143+
if p.fract() == 0.0 {
144+
let p_int = p as i64;
145+
if p_int % 2 == 0 {
146+
// y is a negative even integer, Flash Player returns -0
147+
return Ok(Value::Number(-0.0));
148+
}
149+
}
150+
}
124151

125152
Ok(f64::powf(n, p).into())
126153
}
@@ -136,3 +163,65 @@ pub fn random<'gc>(
136163
let rand = activation.context.rng.random_range(0..MAX_VAL);
137164
Ok(((rand as f64) / (MAX_VAL as f64 + 1f64)).into())
138165
}
166+
167+
pub fn abs<'gc>(
168+
_activation: &mut Activation<'_, 'gc>,
169+
_this: Value<'gc>,
170+
args: &[Value<'gc>],
171+
) -> Result<Value<'gc>, Error<'gc>> {
172+
let input = args[0];
173+
match input {
174+
Value::Integer(i) => Ok(i.abs().into()),
175+
Value::Number(-0.0) => Ok(Value::Integer(0)),
176+
_ => Ok(f64::abs(input.as_f64()).into()),
177+
}
178+
}
179+
180+
pub fn ceil<'gc>(
181+
_activation: &mut Activation<'_, 'gc>,
182+
_this: Value<'gc>,
183+
args: &[Value<'gc>],
184+
) -> Result<Value<'gc>, Error<'gc>> {
185+
let input = args[0];
186+
let num = input.as_f64();
187+
188+
if num.is_nan() {
189+
Ok(Value::Number(f64::NAN))
190+
} else if num.is_infinite() {
191+
Ok(Value::Number(num))
192+
} else {
193+
let ceiled = num.ceil();
194+
// Special case: if input was negative and ceil result is 0, preserve -0.0
195+
if ceiled == 0.0 && num.is_sign_negative() {
196+
Ok(Value::Number(-0.0))
197+
} else if ceiled >= i32::MIN as f64 && ceiled <= i32::MAX as f64 {
198+
Ok(Value::Integer(ceiled as i32))
199+
} else {
200+
Ok(Value::Number(ceiled))
201+
}
202+
}
203+
}
204+
205+
pub fn floor<'gc>(
206+
_activation: &mut Activation<'_, 'gc>,
207+
_this: Value<'gc>,
208+
args: &[Value<'gc>],
209+
) -> Result<Value<'gc>, Error<'gc>> {
210+
let input = args[0];
211+
let num = input.as_f64();
212+
213+
if num.is_nan() {
214+
Ok(Value::Number(f64::NAN))
215+
} else if num.is_infinite() {
216+
Ok(Value::Number(num))
217+
} else if num == 0.0 && num.is_sign_negative() {
218+
Ok(Value::Number(-0.0))
219+
} else {
220+
let floored = num.floor();
221+
if floored >= i32::MIN as f64 && floored <= i32::MAX as f64 {
222+
Ok(Value::Integer(floored as i32))
223+
} else {
224+
Ok(Value::Number(floored))
225+
}
226+
}
227+
}

core/src/avm2/globals/number.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crate::avm2::value::Value;
77
use crate::avm2::{AvmString, Error};
88
use ruffle_macros::istr;
99

10+
use crate::avm2::globals::math;
11+
1012
pub fn number_constructor<'gc>(
1113
activation: &mut Activation<'_, 'gc>,
1214
args: &[Value<'gc>],
@@ -33,6 +35,25 @@ pub fn call_handler<'gc>(
3335
.into())
3436
}
3537

38+
macro_rules! define_math_functions {
39+
($($name:ident),* $(,)?) => {
40+
$(
41+
pub fn $name<'gc>(
42+
activation: &mut Activation<'_, 'gc>,
43+
this: Value<'gc>,
44+
args: &[Value<'gc>],
45+
) -> Result<Value<'gc>, Error<'gc>> {
46+
math::$name(activation, this, args)
47+
}
48+
)*
49+
};
50+
}
51+
52+
define_math_functions!(
53+
abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin,
54+
sqrt, tan
55+
);
56+
3657
/// Implements `Number.toExponential`
3758
pub fn to_exponential<'gc>(
3859
activation: &mut Activation<'_, 'gc>,
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
num_ticks = 1
2-
known_failure = true
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
num_ticks = 1
2-
known_failure = true
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
num_ticks = 1
2-
known_failure = true
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
num_ticks = 1
2-
known_failure = true
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
num_ticks = 1
2-
known_failure = true
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
num_ticks = 1
2-
known_failure = true
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
num_ticks = 1
2-
known_failure = true

0 commit comments

Comments
 (0)