@@ -23,8 +23,9 @@ final global = UnmodifiableListView([
23
23
24
24
/// The Sass math module.
25
25
final module = BuiltInModule ("math" , functions: [
26
- _abs, _ceil, _clamp, _compatible, _floor, _hypot, _isUnitless, _log, _max, //
27
- _min, _pow, _percentage, _randomFunction, _round, _sqrt, _unit,
26
+ _abs, _acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, //
27
+ _floor, _hypot, _isUnitless, _log, _max, _min, _percentage, _pow, //
28
+ _randomFunction, _round, _sin, _sqrt, _tan, _unit,
28
29
], variables: {
29
30
"e" : SassNumber (math.e),
30
31
"pi" : SassNumber (math.pi),
@@ -51,7 +52,6 @@ final _clamp = BuiltInCallable("clamp", r"$min, $number, $max", (arguments) {
51
52
var min = arguments[0 ].assertNumber ("min" );
52
53
var number = arguments[1 ].assertNumber ("number" );
53
54
var max = arguments[2 ].assertNumber ("max" );
54
-
55
55
if (min.hasUnits == number.hasUnits && number.hasUnits == max.hasUnits) {
56
56
if (min.greaterThanOrEquals (max).isTruthy) return min;
57
57
if (min.greaterThanOrEquals (number).isTruthy) return min;
@@ -63,7 +63,6 @@ final _clamp = BuiltInCallable("clamp", r"$min, $number, $max", (arguments) {
63
63
var arg2Name = min.hasUnits != number.hasUnits ? "\$ number" : "\$ max" ;
64
64
var unit1 = min.hasUnits ? "has unit ${min .unitString }" : "is unitless" ;
65
65
var unit2 = arg2.hasUnits ? "has unit ${arg2 .unitString }" : "is unitless" ;
66
-
67
66
throw SassScriptException (
68
67
"\$ min $unit1 but $arg2Name $unit2 . Arguments must all have units or all "
69
68
"be unitless." );
@@ -102,19 +101,19 @@ final _abs = _numberFunction("abs", (value) => value.abs());
102
101
final _hypot = BuiltInCallable ("hypot" , r"$numbers..." , (arguments) {
103
102
var numbers =
104
103
arguments[0 ].asList.map ((argument) => argument.assertNumber ()).toList ();
105
-
106
104
if (numbers.isEmpty) {
107
105
throw SassScriptException ("At least one argument must be passed." );
108
106
}
109
107
110
108
var numeratorUnits = numbers[0 ].numeratorUnits;
111
109
var denominatorUnits = numbers[0 ].denominatorUnits;
112
110
var subtotal = 0.0 ;
113
-
114
111
for (var i = 0 ; i < numbers.length; i++ ) {
115
112
var number = numbers[i];
116
-
117
- if (number.hasUnits != numbers[0 ].hasUnits) {
113
+ if (number.hasUnits == numbers[0 ].hasUnits) {
114
+ number = number.coerce (numeratorUnits, denominatorUnits);
115
+ subtotal += math.pow (number.value, 2 );
116
+ } else {
118
117
var unit1 = numbers[0 ].hasUnits
119
118
? "has unit ${numbers [0 ].unitString }"
120
119
: "is unitless" ;
@@ -124,11 +123,7 @@ final _hypot = BuiltInCallable("hypot", r"$numbers...", (arguments) {
124
123
"Argument 1 $unit1 but argument ${i + 1 } $unit2 . Arguments must all "
125
124
"have units or all be unitless." );
126
125
}
127
-
128
- number = number.coerce (numeratorUnits, denominatorUnits);
129
- subtotal += math.pow (number.value, 2 );
130
126
}
131
-
132
127
return SassNumber .withUnits (math.sqrt (subtotal),
133
128
numeratorUnits: numeratorUnits, denominatorUnits: denominatorUnits);
134
129
});
@@ -143,46 +138,37 @@ final _log = BuiltInCallable("log", r"$number, $base: null", (arguments) {
143
138
throw SassScriptException ("\$ number: Expected $number to have no units." );
144
139
}
145
140
146
- var numberValue = fuzzyEquals (number.value, 0 ) ? 0 : number.value;
147
-
141
+ var numberValue = fuzzyRoundIfZero (number.value);
148
142
if (arguments[1 ] == sassNull) return SassNumber (math.log (numberValue));
149
143
150
144
var base = arguments[1 ].assertNumber ("base" );
151
145
if (base .hasUnits) {
152
146
throw SassScriptException ("\$ base: Expected $base to have no units." );
153
147
}
154
148
155
- var baseValue = fuzzyEquals (base .value, 0 ) || fuzzyEquals ( base .value, 1 )
149
+ var baseValue = fuzzyEquals (base .value, 1 )
156
150
? fuzzyRound (base .value)
157
- : base .value;
158
-
151
+ : fuzzyRoundIfZero (base .value);
159
152
return SassNumber (math.log (numberValue) / math.log (baseValue));
160
153
});
161
154
162
155
final _pow = BuiltInCallable ("pow" , r"$base, $exponent" , (arguments) {
163
156
var base = arguments[0 ].assertNumber ("base" );
164
157
var exponent = arguments[1 ].assertNumber ("exponent" );
165
-
166
158
if (base .hasUnits) {
167
159
throw SassScriptException ("\$ base: Expected $base to have no units." );
168
160
} else if (exponent.hasUnits) {
169
161
throw SassScriptException (
170
162
"\$ exponent: Expected $exponent to have no units." );
171
163
}
172
164
173
- var baseValue = base .value;
174
- var exponentValue = exponent.value;
175
-
176
- if (fuzzyEquals (baseValue.abs (), 1 ) && exponentValue.isInfinite) {
177
- return SassNumber (double .nan);
178
- }
179
-
180
165
// Exponentiating certain real numbers leads to special behaviors. Ensure that
181
166
// these behaviors are consistent for numbers within the precision limit.
182
- if (fuzzyEquals (exponentValue, 0 )) {
183
- exponentValue = 0 ;
167
+ var baseValue = fuzzyRoundIfZero (base .value);
168
+ var exponentValue = fuzzyRoundIfZero (exponent.value);
169
+ if (fuzzyEquals (baseValue.abs (), 1 ) && exponentValue.isInfinite) {
170
+ return SassNumber (double .nan);
184
171
} else if (fuzzyEquals (baseValue, 0 )) {
185
- baseValue = baseValue.isNegative ? - 0.0 : 0 ;
186
172
if (exponentValue.isFinite &&
187
173
fuzzyIsInt (exponentValue) &&
188
174
fuzzyAsInt (exponentValue) % 2 == 1 ) {
@@ -200,7 +186,6 @@ final _pow = BuiltInCallable("pow", r"$base, $exponent", (arguments) {
200
186
fuzzyAsInt (exponentValue) % 2 == 1 ) {
201
187
exponentValue = fuzzyRound (exponentValue);
202
188
}
203
-
204
189
return SassNumber (math.pow (baseValue, exponentValue));
205
190
});
206
191
@@ -210,9 +195,108 @@ final _sqrt = BuiltInCallable("sqrt", r"$number", (arguments) {
210
195
throw SassScriptException ("\$ number: Expected $number to have no units." );
211
196
}
212
197
213
- return SassNumber (math.sqrt (number.value));
198
+ var numberValue = fuzzyRoundIfZero (number.value);
199
+ return SassNumber (math.sqrt (numberValue));
200
+ });
201
+
202
+ num fuzzyRoundIfZero (num number) {
203
+ if (! fuzzyEquals (number, 0 )) return number;
204
+ return number.isNegative ? - 0.0 : 0 ;
205
+ }
206
+
207
+ ///
208
+ /// Trigonometric functions
209
+ ///
210
+
211
+ final _acos = BuiltInCallable ("acos" , r"$number" , (arguments) {
212
+ var number = arguments[0 ].assertNumber ("number" );
213
+ if (number.hasUnits) {
214
+ throw SassScriptException ("\$ number: Expected $number to have no units." );
215
+ }
216
+
217
+ var numberValue = fuzzyEquals (number.value.abs (), 1 )
218
+ ? fuzzyRound (number.value)
219
+ : number.value;
220
+ var acos = math.acos (numberValue) * 180 / math.pi;
221
+ return SassNumber .withUnits (acos, numeratorUnits: ['deg' ]);
214
222
});
215
223
224
+ final _asin = BuiltInCallable ("asin" , r"$number" , (arguments) {
225
+ var number = arguments[0 ].assertNumber ("number" );
226
+ if (number.hasUnits) {
227
+ throw SassScriptException ("\$ number: Expected $number to have no units." );
228
+ }
229
+
230
+ var numberValue = fuzzyEquals (number.value.abs (), 1 )
231
+ ? fuzzyRound (number.value)
232
+ : fuzzyRoundIfZero (number.value);
233
+ var asin = math.asin (numberValue) * 180 / math.pi;
234
+ return SassNumber .withUnits (asin, numeratorUnits: ['deg' ]);
235
+ });
236
+
237
+ final _atan = BuiltInCallable ("atan" , r"$number" , (arguments) {
238
+ var number = arguments[0 ].assertNumber ("number" );
239
+ if (number.hasUnits) {
240
+ throw SassScriptException ("\$ number: Expected $number to have no units." );
241
+ }
242
+
243
+ var numberValue = fuzzyRoundIfZero (number.value);
244
+ var atan = math.atan (numberValue) * 180 / math.pi;
245
+ return SassNumber .withUnits (atan, numeratorUnits: ['deg' ]);
246
+ });
247
+
248
+ final _atan2 = BuiltInCallable ("atan2" , r"$y, $x" , (arguments) {
249
+ var y = arguments[0 ].assertNumber ("y" );
250
+ var x = arguments[1 ].assertNumber ("x" );
251
+ if (y.hasUnits != x.hasUnits) {
252
+ var unit1 = y.hasUnits ? "has unit ${y .unitString }" : "is unitless" ;
253
+ var unit2 = x.hasUnits ? "has unit ${x .unitString }" : "is unitless" ;
254
+ throw SassScriptException (
255
+ "\$ y $unit1 but \$ x $unit2 . Arguments must all have units or all be "
256
+ "unitless." );
257
+ }
258
+
259
+ x = x.coerce (y.numeratorUnits, y.denominatorUnits);
260
+ var xValue = fuzzyRoundIfZero (x.value);
261
+ var yValue = fuzzyRoundIfZero (y.value);
262
+ var atan2 = math.atan2 (yValue, xValue) * 180 / math.pi;
263
+ return SassNumber .withUnits (atan2, numeratorUnits: ['deg' ]);
264
+ });
265
+
266
+ final _cos = BuiltInCallable ("cos" , r"$number" , (arguments) {
267
+ var number = _coerceToRad (arguments[0 ].assertNumber ("number" ));
268
+ return SassNumber (math.cos (number.value));
269
+ });
270
+
271
+ final _sin = BuiltInCallable ("sin" , r"$number" , (arguments) {
272
+ var number = _coerceToRad (arguments[0 ].assertNumber ("number" ));
273
+ var numberValue = fuzzyRoundIfZero (number.value);
274
+ return SassNumber (math.sin (numberValue));
275
+ });
276
+
277
+ final _tan = BuiltInCallable ("tan" , r"$number" , (arguments) {
278
+ var number = _coerceToRad (arguments[0 ].assertNumber ("number" ));
279
+ var asymptoteInterval = 0.5 * math.pi;
280
+ var tanPeriod = 2 * math.pi;
281
+ if (fuzzyEquals ((number.value - asymptoteInterval) % tanPeriod, 0 )) {
282
+ return SassNumber (double .infinity);
283
+ } else if (fuzzyEquals ((number.value + asymptoteInterval) % tanPeriod, 0 )) {
284
+ return SassNumber (double .negativeInfinity);
285
+ } else {
286
+ var numberValue = fuzzyRoundIfZero (number.value);
287
+ return SassNumber (math.tan (numberValue));
288
+ }
289
+ });
290
+
291
+ SassNumber _coerceToRad (SassNumber number) {
292
+ try {
293
+ return number.coerce (['rad' ], []);
294
+ } on SassScriptException catch (error) {
295
+ if (! error.message.startsWith ('Incompatible units' )) rethrow ;
296
+ throw SassScriptException ('\$ number: Expected ${number } to be an angle.' );
297
+ }
298
+ }
299
+
216
300
///
217
301
/// Unit functions
218
302
///
0 commit comments