mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
aa88c156e6
Summary: Math functions like SUM which call `_chain` were catching `TypeError`s raised by the iterable arguments themselves, e.g. `SUM(r.A / r.B for r in $group)` where `r.A / r.B` raises a `TypeError` would silently return wrong results. This diff narrows the `try/catch` to only check whether the argument is iterable as intended, but not catch errors from the process of iterating. Test Plan: Added Python unit test. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3679
888 lines
17 KiB
Python
888 lines
17 KiB
Python
# pylint: disable=unused-argument
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import datetime
|
|
import math as _math
|
|
import operator
|
|
import os
|
|
import random
|
|
import uuid
|
|
from functools import reduce
|
|
|
|
from six.moves import zip, xrange
|
|
import six
|
|
|
|
from functions.info import ISNUMBER, ISLOGICAL
|
|
from functions.unimplemented import unimplemented
|
|
import roman
|
|
|
|
# Iterates through elements of iterable arguments, or through individual args when not iterable.
|
|
def _chain(*values_or_iterables):
|
|
for v in values_or_iterables:
|
|
try:
|
|
v = iter(v)
|
|
except TypeError:
|
|
yield v
|
|
else:
|
|
for x in v:
|
|
yield x
|
|
|
|
|
|
# Iterates through iterable or other arguments, skipping non-numeric ones.
|
|
def _chain_numeric(*values_or_iterables):
|
|
for v in _chain(*values_or_iterables):
|
|
if ISNUMBER(v) and not ISLOGICAL(v):
|
|
yield v
|
|
|
|
|
|
# Iterates through iterable or other arguments, replacing non-numeric ones with 0 (or True with 1).
|
|
def _chain_numeric_a(*values_or_iterables):
|
|
for v in _chain(*values_or_iterables):
|
|
yield int(v) if ISLOGICAL(v) else v if ISNUMBER(v) else 0
|
|
|
|
|
|
# Iterates through iterable or other arguments, only including numbers, dates, and datetimes.
|
|
def _chain_numeric_or_date(*values_or_iterables):
|
|
for v in _chain(*values_or_iterables):
|
|
if ISNUMBER(v) and not ISLOGICAL(v) or isinstance(v, (datetime.date, datetime.datetime)):
|
|
yield v
|
|
|
|
|
|
def _round_toward_zero(value):
|
|
return _math.floor(value) if value >= 0 else _math.ceil(value)
|
|
|
|
def _round_away_from_zero(value):
|
|
return _math.ceil(value) if value >= 0 else _math.floor(value)
|
|
|
|
def ABS(value):
|
|
"""
|
|
Returns the absolute value of a number.
|
|
|
|
>>> ABS(2)
|
|
2
|
|
>>> ABS(-2)
|
|
2
|
|
>>> ABS(-4)
|
|
4
|
|
"""
|
|
return abs(value)
|
|
|
|
def ACOS(value):
|
|
"""
|
|
Returns the inverse cosine of a value, in radians.
|
|
|
|
>>> round(ACOS(-0.5), 9)
|
|
2.094395102
|
|
>>> round(ACOS(-0.5)*180/PI(), 10)
|
|
120.0
|
|
"""
|
|
return _math.acos(value)
|
|
|
|
def ACOSH(value):
|
|
"""
|
|
Returns the inverse hyperbolic cosine of a number.
|
|
|
|
>>> ACOSH(1)
|
|
0.0
|
|
>>> round(ACOSH(10), 7)
|
|
2.9932228
|
|
"""
|
|
return _math.acosh(value)
|
|
|
|
def ARABIC(roman_numeral):
|
|
"""
|
|
Computes the value of a Roman numeral.
|
|
|
|
>>> ARABIC("LVII")
|
|
57
|
|
>>> ARABIC('mcmxii')
|
|
1912
|
|
"""
|
|
return roman.fromRoman(roman_numeral.upper())
|
|
|
|
def ASIN(value):
|
|
"""
|
|
Returns the inverse sine of a value, in radians.
|
|
|
|
>>> round(ASIN(-0.5), 9)
|
|
-0.523598776
|
|
>>> round(ASIN(-0.5)*180/PI(), 10)
|
|
-30.0
|
|
>>> round(DEGREES(ASIN(-0.5)), 10)
|
|
-30.0
|
|
"""
|
|
return _math.asin(value)
|
|
|
|
def ASINH(value):
|
|
"""
|
|
Returns the inverse hyperbolic sine of a number.
|
|
|
|
>>> round(ASINH(-2.5), 9)
|
|
-1.647231146
|
|
>>> round(ASINH(10), 9)
|
|
2.99822295
|
|
"""
|
|
return _math.asinh(value)
|
|
|
|
def ATAN(value):
|
|
"""
|
|
Returns the inverse tangent of a value, in radians.
|
|
|
|
>>> round(ATAN(1), 9)
|
|
0.785398163
|
|
>>> ATAN(1)*180/PI()
|
|
45.0
|
|
>>> DEGREES(ATAN(1))
|
|
45.0
|
|
"""
|
|
return _math.atan(value)
|
|
|
|
def ATAN2(x, y):
|
|
"""
|
|
Returns the angle between the x-axis and a line segment from the origin (0,0) to specified
|
|
coordinate pair (`x`,`y`), in radians.
|
|
|
|
>>> round(ATAN2(1, 1), 9)
|
|
0.785398163
|
|
>>> round(ATAN2(-1, -1), 9)
|
|
-2.35619449
|
|
>>> ATAN2(-1, -1)*180/PI()
|
|
-135.0
|
|
>>> DEGREES(ATAN2(-1, -1))
|
|
-135.0
|
|
>>> round(ATAN2(1,2), 9)
|
|
1.107148718
|
|
"""
|
|
return _math.atan2(y, x)
|
|
|
|
def ATANH(value):
|
|
"""
|
|
Returns the inverse hyperbolic tangent of a number.
|
|
|
|
>>> round(ATANH(0.76159416), 9)
|
|
1.00000001
|
|
>>> round(ATANH(-0.1), 9)
|
|
-0.100335348
|
|
"""
|
|
return _math.atanh(value)
|
|
|
|
def CEILING(value, factor=1):
|
|
"""
|
|
Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is
|
|
omitted or 1.
|
|
|
|
>>> CEILING(2.5, 1)
|
|
3
|
|
>>> CEILING(-2.5, -2)
|
|
-4
|
|
>>> CEILING(-2.5, 2)
|
|
-2
|
|
>>> CEILING(1.5, 0.1)
|
|
1.5
|
|
>>> CEILING(0.234, 0.01)
|
|
0.24
|
|
"""
|
|
return int(_math.ceil(float(value) / factor)) * factor
|
|
|
|
def COMBIN(n, k):
|
|
"""
|
|
Returns the number of ways to choose some number of objects from a pool of a given size of
|
|
objects.
|
|
|
|
>>> COMBIN(8,2)
|
|
28
|
|
>>> COMBIN(4,2)
|
|
6
|
|
>>> COMBIN(10,7)
|
|
120
|
|
"""
|
|
# From http://stackoverflow.com/a/4941932/328565
|
|
k = min(k, n-k)
|
|
if k == 0:
|
|
return 1
|
|
numer = reduce(operator.mul, xrange(n, n-k, -1))
|
|
denom = reduce(operator.mul, xrange(1, k+1))
|
|
return numer//denom
|
|
|
|
def COS(angle):
|
|
"""
|
|
Returns the cosine of an angle provided in radians.
|
|
|
|
>>> round(COS(1.047), 7)
|
|
0.5001711
|
|
>>> round(COS(60*PI()/180), 10)
|
|
0.5
|
|
>>> round(COS(RADIANS(60)), 10)
|
|
0.5
|
|
"""
|
|
return _math.cos(angle)
|
|
|
|
def COSH(value):
|
|
"""
|
|
Returns the hyperbolic cosine of any real number.
|
|
|
|
>>> round(COSH(4), 6)
|
|
27.308233
|
|
>>> round(COSH(EXP(1)), 7)
|
|
7.6101251
|
|
"""
|
|
return _math.cosh(value)
|
|
|
|
def DEGREES(angle):
|
|
"""
|
|
Converts an angle value in radians to degrees.
|
|
|
|
>>> round(DEGREES(ACOS(-0.5)), 10)
|
|
120.0
|
|
>>> DEGREES(PI())
|
|
180.0
|
|
"""
|
|
return _math.degrees(angle)
|
|
|
|
def EVEN(value):
|
|
"""
|
|
Rounds a number up to the nearest even integer, rounding away from zero.
|
|
|
|
>>> EVEN(1.5)
|
|
2
|
|
>>> EVEN(3)
|
|
4
|
|
>>> EVEN(2)
|
|
2
|
|
>>> EVEN(-1)
|
|
-2
|
|
"""
|
|
return int(_round_away_from_zero(float(value) / 2)) * 2
|
|
|
|
def EXP(exponent):
|
|
"""
|
|
Returns Euler's number, e (~2.718) raised to a power.
|
|
|
|
>>> round(EXP(1), 8)
|
|
2.71828183
|
|
>>> round(EXP(2), 7)
|
|
7.3890561
|
|
"""
|
|
return _math.exp(exponent)
|
|
|
|
def FACT(value):
|
|
"""
|
|
Returns the factorial of a number.
|
|
|
|
>>> FACT(5)
|
|
120
|
|
>>> FACT(1.9)
|
|
1
|
|
>>> FACT(0)
|
|
1
|
|
>>> FACT(1)
|
|
1
|
|
>>> FACT(-1)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: factorial() not defined for negative values
|
|
"""
|
|
return _math.factorial(int(value))
|
|
|
|
def FACTDOUBLE(value):
|
|
"""
|
|
Returns the "double factorial" of a number.
|
|
|
|
>>> FACTDOUBLE(6)
|
|
48
|
|
>>> FACTDOUBLE(7)
|
|
105
|
|
>>> FACTDOUBLE(3)
|
|
3
|
|
>>> FACTDOUBLE(4)
|
|
8
|
|
"""
|
|
return reduce(operator.mul, xrange(value, 1, -2))
|
|
|
|
def FLOOR(value, factor=1):
|
|
"""
|
|
Rounds a number down to the nearest integer multiple of specified significance.
|
|
|
|
>>> FLOOR(3.7,2)
|
|
2
|
|
>>> FLOOR(-2.5,-2)
|
|
-2
|
|
>>> FLOOR(2.5,-2)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: factor argument invalid
|
|
>>> FLOOR(1.58,0.1)
|
|
1.5
|
|
>>> FLOOR(0.234,0.01)
|
|
0.23
|
|
"""
|
|
if (factor < 0) != (value < 0):
|
|
raise ValueError("factor argument invalid")
|
|
return int(_math.floor(float(value) / factor)) * factor
|
|
|
|
def _gcd(a, b):
|
|
while a != 0:
|
|
if a > b:
|
|
a, b = b, a
|
|
a, b = b % a, a
|
|
return b
|
|
|
|
def GCD(value1, *more_values):
|
|
"""
|
|
Returns the greatest common divisor of one or more integers.
|
|
|
|
>>> GCD(5, 2)
|
|
1
|
|
>>> GCD(24, 36)
|
|
12
|
|
>>> GCD(7, 1)
|
|
1
|
|
>>> GCD(5, 0)
|
|
5
|
|
>>> GCD(0, 5)
|
|
5
|
|
>>> GCD(5)
|
|
5
|
|
>>> GCD(14, 42, 21)
|
|
7
|
|
"""
|
|
values = [v for v in (value1,) + more_values if v]
|
|
if not values:
|
|
return 0
|
|
if any(v < 0 for v in values):
|
|
raise ValueError("gcd requires non-negative values")
|
|
return reduce(_gcd, map(int, values))
|
|
|
|
def INT(value):
|
|
"""
|
|
Rounds a number down to the nearest integer that is less than or equal to it.
|
|
|
|
>>> INT(8.9)
|
|
8
|
|
>>> INT(-8.9)
|
|
-9
|
|
>>> 19.5-INT(19.5)
|
|
0.5
|
|
"""
|
|
return int(_math.floor(value))
|
|
|
|
def _lcm(a, b):
|
|
return a * b // _gcd(a, b)
|
|
|
|
def LCM(value1, *more_values):
|
|
"""
|
|
Returns the least common multiple of one or more integers.
|
|
|
|
>>> LCM(5, 2)
|
|
10
|
|
>>> LCM(24, 36)
|
|
72
|
|
>>> LCM(0, 5)
|
|
0
|
|
>>> LCM(5)
|
|
5
|
|
>>> LCM(10, 100)
|
|
100
|
|
>>> LCM(12, 18)
|
|
36
|
|
>>> LCM(12, 18, 24)
|
|
72
|
|
"""
|
|
values = (value1,) + more_values
|
|
if any(v < 0 for v in values):
|
|
raise ValueError("gcd requires non-negative values")
|
|
if any(v == 0 for v in values):
|
|
return 0
|
|
return reduce(_lcm, map(int, values))
|
|
|
|
def LN(value):
|
|
"""
|
|
Returns the the logarithm of a number, base e (Euler's number).
|
|
|
|
>>> round(LN(86), 7)
|
|
4.4543473
|
|
>>> round(LN(2.7182818), 7)
|
|
1.0
|
|
>>> round(LN(EXP(3)), 10)
|
|
3.0
|
|
"""
|
|
return _math.log(value)
|
|
|
|
def LOG(value, base=10):
|
|
"""
|
|
Returns the the logarithm of a number given a base.
|
|
|
|
>>> LOG(10)
|
|
1.0
|
|
>>> LOG(8, 2)
|
|
3.0
|
|
>>> round(LOG(86, 2.7182818), 7)
|
|
4.4543473
|
|
"""
|
|
return _math.log(value, base)
|
|
|
|
def LOG10(value):
|
|
"""
|
|
Returns the the logarithm of a number, base 10.
|
|
|
|
>>> round(LOG10(86), 9)
|
|
1.934498451
|
|
>>> LOG10(10)
|
|
1.0
|
|
>>> LOG10(100000)
|
|
5.0
|
|
>>> LOG10(10**5)
|
|
5.0
|
|
"""
|
|
return _math.log10(value)
|
|
|
|
def MOD(dividend, divisor):
|
|
"""
|
|
Returns the result of the modulo operator, the remainder after a division operation.
|
|
|
|
>>> MOD(3, 2)
|
|
1
|
|
>>> MOD(-3, 2)
|
|
1
|
|
>>> MOD(3, -2)
|
|
-1
|
|
>>> MOD(-3, -2)
|
|
-1
|
|
"""
|
|
return dividend % divisor
|
|
|
|
def MROUND(value, factor):
|
|
"""
|
|
Rounds one number to the nearest integer multiple of another.
|
|
|
|
>>> MROUND(10, 3)
|
|
9
|
|
>>> MROUND(-10, -3)
|
|
-9
|
|
>>> round(MROUND(1.3, 0.2), 10)
|
|
1.4
|
|
>>> MROUND(5, -2)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: factor argument invalid
|
|
"""
|
|
if (factor < 0) != (value < 0):
|
|
raise ValueError("factor argument invalid")
|
|
return int(_round_toward_zero(float(value) / factor + 0.5)) * factor
|
|
|
|
def MULTINOMIAL(value1, *more_values):
|
|
"""
|
|
Returns the factorial of the sum of values divided by the product of the values' factorials.
|
|
|
|
>>> MULTINOMIAL(2, 3, 4)
|
|
1260
|
|
>>> MULTINOMIAL(3)
|
|
1
|
|
>>> MULTINOMIAL(1,2,3)
|
|
60
|
|
>>> MULTINOMIAL(0,2,4,6)
|
|
13860
|
|
"""
|
|
s = value1
|
|
res = 1
|
|
for v in more_values:
|
|
s += v
|
|
res *= COMBIN(s, v)
|
|
return res
|
|
|
|
def ODD(value):
|
|
"""
|
|
Rounds a number up to the nearest odd integer.
|
|
|
|
>>> ODD(1.5)
|
|
3
|
|
>>> ODD(3)
|
|
3
|
|
>>> ODD(2)
|
|
3
|
|
>>> ODD(-1)
|
|
-1
|
|
>>> ODD(-2)
|
|
-3
|
|
"""
|
|
return int(_round_away_from_zero(float(value + 1) / 2)) * 2 - 1
|
|
|
|
def PI():
|
|
"""
|
|
Returns the value of Pi to 14 decimal places.
|
|
|
|
>>> round(PI(), 9)
|
|
3.141592654
|
|
>>> round(PI()/2, 9)
|
|
1.570796327
|
|
>>> round(PI()*9, 8)
|
|
28.27433388
|
|
"""
|
|
return _math.pi
|
|
|
|
def POWER(base, exponent):
|
|
"""
|
|
Returns a number raised to a power.
|
|
|
|
>>> POWER(5,2)
|
|
25.0
|
|
>>> round(POWER(98.6,3.2), 3)
|
|
2401077.222
|
|
>>> round(POWER(4,5.0/4), 9)
|
|
5.656854249
|
|
"""
|
|
return _math.pow(base, exponent)
|
|
|
|
|
|
def PRODUCT(factor1, *more_factors):
|
|
"""
|
|
Returns the result of multiplying a series of numbers together. Each argument may be a number or
|
|
an array.
|
|
|
|
>>> PRODUCT([5,15,30])
|
|
2250
|
|
>>> PRODUCT([5,15,30], 2)
|
|
4500
|
|
>>> PRODUCT(5,15,[30],[2])
|
|
4500
|
|
|
|
More tests:
|
|
>>> PRODUCT([2, True, None, "", False, "0", 5])
|
|
10
|
|
>>> PRODUCT([2, True, None, "", False, 0, 5])
|
|
0
|
|
"""
|
|
return reduce(operator.mul, _chain_numeric(factor1, *more_factors))
|
|
|
|
def QUOTIENT(dividend, divisor):
|
|
"""
|
|
Returns one number divided by another, without the remainder.
|
|
|
|
>>> QUOTIENT(5, 2)
|
|
2
|
|
>>> QUOTIENT(4.5, 3.1)
|
|
1
|
|
>>> QUOTIENT(-10, 3)
|
|
-3
|
|
"""
|
|
return TRUNC(float(dividend) / divisor)
|
|
|
|
def RADIANS(angle):
|
|
"""
|
|
Converts an angle value in degrees to radians.
|
|
|
|
>>> round(RADIANS(270), 6)
|
|
4.712389
|
|
"""
|
|
return _math.radians(angle)
|
|
|
|
def RAND():
|
|
"""
|
|
Returns a random number between 0 inclusive and 1 exclusive.
|
|
"""
|
|
return random.random()
|
|
|
|
def RANDBETWEEN(low, high):
|
|
"""
|
|
Returns a uniformly random integer between two values, inclusive.
|
|
"""
|
|
return random.randrange(low, high + 1)
|
|
|
|
def ROMAN(number, form_unused=None):
|
|
"""
|
|
Formats a number in Roman numerals. The second argument is ignored in this implementation.
|
|
|
|
>>> ROMAN(499,0)
|
|
'CDXCIX'
|
|
>>> ROMAN(499.2,0)
|
|
'CDXCIX'
|
|
>>> ROMAN(57)
|
|
'LVII'
|
|
>>> ROMAN(1912)
|
|
'MCMXII'
|
|
"""
|
|
# TODO: Maybe we should support the second argument.
|
|
return roman.toRoman(int(number))
|
|
|
|
def ROUND(value, places=0):
|
|
"""
|
|
Rounds a number to a certain number of decimal places,
|
|
by default to the nearest whole number if the number of places is not given.
|
|
|
|
Rounds away from zero ('up' for positive numbers)
|
|
in the case of a tie, i.e. when the last digit is 5.
|
|
|
|
>>> ROUND(1.4)
|
|
1.0
|
|
>>> ROUND(1.5)
|
|
2.0
|
|
>>> ROUND(2.5)
|
|
3.0
|
|
>>> ROUND(-2.5)
|
|
-3.0
|
|
>>> ROUND(2.15, 1)
|
|
2.2
|
|
>>> ROUND(-1.475, 2)
|
|
-1.48
|
|
>>> ROUND(21.5, -1)
|
|
20.0
|
|
>>> ROUND(626.3,-3)
|
|
1000.0
|
|
>>> ROUND(1.98,-1)
|
|
0.0
|
|
>>> ROUND(-50.55,-2)
|
|
-100.0
|
|
>>> ROUND(0)
|
|
0.0
|
|
"""
|
|
p = 10 ** places
|
|
if value >= 0:
|
|
return float(_math.floor((value * p) + 0.5)) / p
|
|
else:
|
|
return float(_math.ceil((value * p) - 0.5)) / p
|
|
|
|
|
|
def ROUNDDOWN(value, places=0):
|
|
"""
|
|
Rounds a number to a certain number of decimal places, always rounding down towards zero.
|
|
|
|
>>> ROUNDDOWN(3.2, 0)
|
|
3
|
|
>>> ROUNDDOWN(76.9,0)
|
|
76
|
|
>>> ROUNDDOWN(3.14159, 3)
|
|
3.141
|
|
>>> ROUNDDOWN(-3.14159, 1)
|
|
-3.1
|
|
>>> ROUNDDOWN(31415.92654, -2)
|
|
31400
|
|
"""
|
|
factor = 10**-places
|
|
return int(_round_toward_zero(float(value) / factor)) * factor
|
|
|
|
def ROUNDUP(value, places=0):
|
|
"""
|
|
Rounds a number to a certain number of decimal places, always rounding up away from zero.
|
|
|
|
>>> ROUNDUP(3.2,0)
|
|
4
|
|
>>> ROUNDUP(76.9,0)
|
|
77
|
|
>>> ROUNDUP(3.14159, 3)
|
|
3.142
|
|
>>> ROUNDUP(-3.14159, 1)
|
|
-3.2
|
|
>>> ROUNDUP(31415.92654, -2)
|
|
31500
|
|
"""
|
|
factor = 10**-places
|
|
return int(_round_away_from_zero(float(value) / factor)) * factor
|
|
|
|
def SERIESSUM(x, n, m, a):
|
|
"""
|
|
Given parameters x, n, m, and a, returns the power series sum a_1*x^n + a_2*x^(n+m)
|
|
+ ... + a_i*x^(n+(i-1)m), where i is the number of entries in range `a`.
|
|
|
|
>>> SERIESSUM(1,0,1,1)
|
|
1
|
|
>>> SERIESSUM(2,1,0,[1,2,3])
|
|
12
|
|
>>> SERIESSUM(-3,1,1,[2,4,6])
|
|
-132
|
|
>>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6)
|
|
0.707103
|
|
"""
|
|
return sum(coef*pow(x, n+i*m) for i, coef in enumerate(_chain(a)))
|
|
|
|
def SIGN(value):
|
|
"""
|
|
Given an input number, returns `-1` if it is negative, `1` if positive, and `0` if it is zero.
|
|
|
|
>>> SIGN(10)
|
|
1
|
|
>>> SIGN(4.0-4.0)
|
|
0
|
|
>>> SIGN(-0.00001)
|
|
-1
|
|
"""
|
|
return 0 if value == 0 else int(_math.copysign(1, value))
|
|
|
|
def SIN(angle):
|
|
"""
|
|
Returns the sine of an angle provided in radians.
|
|
|
|
>>> round(SIN(PI()), 10)
|
|
0.0
|
|
>>> SIN(PI()/2)
|
|
1.0
|
|
>>> round(SIN(30*PI()/180), 10)
|
|
0.5
|
|
>>> round(SIN(RADIANS(30)), 10)
|
|
0.5
|
|
"""
|
|
return _math.sin(angle)
|
|
|
|
def SINH(value):
|
|
"""
|
|
Returns the hyperbolic sine of any real number.
|
|
|
|
>>> round(2.868*SINH(0.0342*1.03), 7)
|
|
0.1010491
|
|
"""
|
|
return _math.sinh(value)
|
|
|
|
def SQRT(value):
|
|
"""
|
|
Returns the positive square root of a positive number.
|
|
|
|
>>> SQRT(16)
|
|
4.0
|
|
>>> SQRT(-16)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: math domain error
|
|
>>> SQRT(ABS(-16))
|
|
4.0
|
|
"""
|
|
return _math.sqrt(value)
|
|
|
|
|
|
def SQRTPI(value):
|
|
"""
|
|
Returns the positive square root of the product of Pi and the given positive number.
|
|
|
|
>>> round(SQRTPI(1), 6)
|
|
1.772454
|
|
>>> round(SQRTPI(2), 6)
|
|
2.506628
|
|
"""
|
|
return _math.sqrt(_math.pi * value)
|
|
|
|
@unimplemented
|
|
def SUBTOTAL(function_code, range1, range2):
|
|
"""
|
|
Returns a subtotal for a vertical range of cells using a specified aggregation function.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
def SUM(value1, *more_values):
|
|
"""
|
|
Returns the sum of a series of numbers. Each argument may be a number or an array.
|
|
Non-numeric values are ignored.
|
|
|
|
>>> SUM([5,15,30])
|
|
50
|
|
>>> SUM([5.,15,30], 2)
|
|
52.0
|
|
>>> SUM(5,15,[30],[2])
|
|
52
|
|
|
|
More tests:
|
|
>>> SUM([10.25, None, "", False, "other", 20.5])
|
|
30.75
|
|
>>> SUM([True, "3", 4], True)
|
|
6
|
|
"""
|
|
return sum(_chain_numeric_a(value1, *more_values))
|
|
|
|
|
|
@unimplemented
|
|
def SUMIF(records, criterion, sum_range):
|
|
"""
|
|
Returns a conditional sum across a range.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def SUMIFS(sum_range, criteria_range1, criterion1, *args):
|
|
"""
|
|
Returns the sum of a range depending on multiple criteria.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def SUMPRODUCT(array1, *more_arrays):
|
|
"""
|
|
Multiplies corresponding components in two equally-sized arrays,
|
|
and returns the sum of those products.
|
|
|
|
>>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3])
|
|
156
|
|
>>> SUMPRODUCT([], [], [])
|
|
0
|
|
>>> SUMPRODUCT([-0.25], [-2], [-3])
|
|
-1.5
|
|
>>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3])
|
|
-3.0
|
|
"""
|
|
return sum(reduce(operator.mul, values) for values in zip(array1, *more_arrays))
|
|
|
|
@unimplemented
|
|
def SUMSQ(value1, value2):
|
|
"""
|
|
Returns the sum of the squares of a series of numbers and/or cells.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def TAN(angle):
|
|
"""
|
|
Returns the tangent of an angle provided in radians.
|
|
|
|
>>> round(TAN(0.785), 8)
|
|
0.99920399
|
|
>>> round(TAN(45*PI()/180), 10)
|
|
1.0
|
|
>>> round(TAN(RADIANS(45)), 10)
|
|
1.0
|
|
"""
|
|
return _math.tan(angle)
|
|
|
|
def TANH(value):
|
|
"""
|
|
Returns the hyperbolic tangent of any real number.
|
|
|
|
>>> round(TANH(-2), 6)
|
|
-0.964028
|
|
>>> TANH(0)
|
|
0.0
|
|
>>> round(TANH(0.5), 6)
|
|
0.462117
|
|
"""
|
|
return _math.tanh(value)
|
|
|
|
def TRUNC(value, places=0):
|
|
"""
|
|
Truncates a number to a certain number of significant digits by omitting less significant
|
|
digits.
|
|
|
|
>>> TRUNC(8.9)
|
|
8
|
|
>>> TRUNC(-8.9)
|
|
-8
|
|
>>> TRUNC(0.45)
|
|
0
|
|
"""
|
|
# TRUNC seems indistinguishable from ROUNDDOWN.
|
|
return ROUNDDOWN(value, places)
|
|
|
|
def UUID():
|
|
"""
|
|
Generate a random UUID-formatted string identifier.
|
|
Since UUID() produces a different value each time it's called, it is best to use it in
|
|
[trigger formula](formulas.md#trigger-formulas) for new records.
|
|
This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula
|
|
may get recalculated any time the document is reloaded, producing a different value for UUID() each time.
|
|
"""
|
|
try:
|
|
uid = uuid.uuid4()
|
|
except Exception:
|
|
# Pynbox doesn't support the above because it doesn't support `os.urandom()`.
|
|
# Using the `random` module is less secure but should be OK.
|
|
if six.PY2:
|
|
byts = [chr(random.randrange(0, 256)) for _ in xrange(0, 16)]
|
|
else:
|
|
byts = bytes([random.randrange(0, 256) for _ in range(0, 16)])
|
|
uid = uuid.UUID(bytes=byts, version=4)
|
|
return str(uid)
|