Synsemadocsv0.6.xENES

Biblioteca estándar

Bytes, matemática y arrays

bytes-math.syn
-- Doc example: bytes, math, and numeric arrays / linear algebra (all pure).
intent: "doc example: bytes, math and arrays"
require random                 -- only for ecdh_keypair (fresh key material); the rest is pure

print("decode(bytes(\"Hi\")) = " + decode(bytes("Hi")) + ",  norm([3,4]) = " + text(norm(array([3, 4]))))

test "bytes <-> text are inverses; hex encoding"
    let b be bytes("Hi")
    assert_eq(decode(b), "Hi")
    assert_eq(decode(bytes("Hi"), "hex"), "4869")
    assert_eq(decode(bytes("4869", "hex")), "Hi")

test "base58/base32 round-trip; bytes <-> exact integers"
    assert_eq(decode(bytes(decode(bytes("Hi"), "base58"), "base58")), "Hi")
    assert_eq(decode(bytes("Hi"), "base32"), "JBUQ")
    -- bytes_to_int/int_to_bytes: big-endian, exact (protocol/signature integers)
    assert_eq(bytes_to_int(bytes("0400", "hex")), 1024)
    assert_eq(int_to_bytes(1024), bytes("0400", "hex"))
    assert_eq(int_to_bytes(7, 4), bytes("00000007", "hex"))

test "hkdf_sha256 matches RFC 5869 test case 1; aes_gcm round-trips and refuses tampering (engine v0.6.20+)"
    let okm be hkdf_sha256(bytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"), bytes("000102030405060708090a0b0c", "hex"), bytes("f0f1f2f3f4f5f6f7f8f9", "hex"), 42)
    assert_eq(decode(okm, "hex"), "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865")
    let key be bytes("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "hex")   -- 32 bytes → AES-256-GCM
    let nonce be bytes("000000000000000000000001", "hex")                                          -- 12 bytes, never reused with a key
    let ct be aes_gcm_encrypt(key, nonce, bytes("hola"), bytes("aad"))
    assert_eq(length(ct), 4 + 16)                                                                   -- ciphertext ‖ 16-byte tag
    assert_eq(decode(aes_gcm_decrypt(key, nonce, ct, bytes("aad"))), "hola")
    assert_error(() => aes_gcm_decrypt(key, nonce, ct, bytes("other-aad")))                         -- wrong aad → error, never partial bytes

test "ecdh_keypair + ecdh_shared_secret: both sides derive the same key (engine v0.6.20+)"
    let a be ecdh_keypair("P-256")                        -- {private: secret, public: bytes} — needs `random`
    let b be ecdh_keypair("P-256")
    assert_eq(length(public of a), 65)                    -- SEC1 uncompressed point
    let k_ab be hkdf_sha256(ecdh_shared_secret(private of a, public of b, "P-256"), bytes("salt"), bytes("ctx"), 32)
    let k_ba be hkdf_sha256(ecdh_shared_secret(private of b, public of a, "P-256"), bytes("salt"), bytes("ctx"), 32)
    -- a shared secret is a `secret` (never printed); derived keys stay sealed too — prove equality by USING them
    let nonce be bytes("000000000000000000000002", "hex")
    assert_eq(decode(aes_gcm_decrypt(k_ba, nonce, aes_gcm_encrypt(k_ab, nonce, bytes("hi")))), "hi")

test "X25519 (RFC 7748 §6.1 vectors): ecdh_public and the same key on both sides (engine v0.6.42+)"
    let alice be bytes("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", "hex")
    let bob be bytes("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb", "hex")
    let alice_pub be ecdh_public(alice, "X25519")
    let bob_pub be ecdh_public(bob, "X25519")
    assert_eq(decode(alice_pub, "hex"), "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
    assert_eq(decode(bob_pub, "hex"), "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")
    let k_ab be hkdf_sha256(ecdh_shared_secret(alice, bob_pub, "X25519"), bytes("salt"), bytes("ctx"), 32)
    let k_ba be hkdf_sha256(ecdh_shared_secret(bob, alice_pub, "X25519"), bytes("salt"), bytes("ctx"), 32)
    let nonce be bytes("000000000000000000000003", "hex")
    assert_eq(decode(aes_gcm_decrypt(k_ba, nonce, aes_gcm_encrypt(k_ab, nonce, bytes("hi")))), "hi")
    assert_eq(length(public of ecdh_keypair("X25519")), 32)   -- a fresh key: 32 bytes

test "hmac returns bytes: hex(mac) or decode(mac, 'hex') shows it; verify_hmac is constant-time"
    let mac be hmac("data", "key")
    assert_eq(type_of(mac), "bytes")
    assert_eq(length(mac), 32)
    assert_eq(hex(mac), "0x5031fe3d989c6d1537a013fa6e739da23463fdaec3b70137d828e36ace221bd0")
    assert_eq(decode(mac, "hex"), "5031fe3d989c6d1537a013fa6e739da23463fdaec3b70137d828e36ace221bd0")
    assert_eq(length(hmac("data", "key", "sha512")), 64)
    assert_error(() => hmac("data", "key", "sha1"))                         -- SHA-1 is rejected
    assert_eq(verify_hmac("data", decode(mac, "hex"), "key"), true)

test "hmac takes bytes as raw bytes: the same MAC as text, and binary keys work (engine v0.6.44+)"
    assert_eq(hex(hmac(bytes("data"), bytes("key"))), "0x5031fe3d989c6d1537a013fa6e739da23463fdaec3b70137d828e36ace221bd0")
    -- RFC 4231 test case 1: a binary key (0x0b × 20).
    let key be bytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex")
    assert_eq(hex(hmac("Hi There", key)), "0xb0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7")
    -- Chaining keys, as AWS SigV4 does, with the key as a secret (never revealed): each MAC
    -- (bytes) is the next key. AWS's own example key and its documented signing key.
    let secret_key be as_secret("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY")
    let k_signing be hmac("aws4_request", hmac("iam", hmac("us-east-1", hmac("20150830", "AWS4" + secret_key))))
    assert_eq(hex(k_signing), "0xc4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9")
    assert_eq(verify_hmac("data", nothing, key), false)                 -- a missing signature is false
    assert_eq(verify_hmac("data", hmac("data", key), key), true)    -- the signature can be the raw bytes
    assert_error(() => hmac(123, "key"))                                -- not text, bytes or a secret: error

test "bits on 64-bit integers and xor_bytes (engine v0.6.44+)"
    assert_eq(bit_xor(0x36, 0x5c), 0x6a)
    assert_eq(bit_and(0xff, 0x0f), 0x0f)
    assert_eq(bit_or(1, 6), 7)
    assert_eq(bit_not(0), -1)
    assert_eq(shl(1, 10), 1024)
    assert_eq(shr(-8, 1), -4)                                           -- arithmetic: keeps the sign
    assert_error(() => shl(1, 64))                                      -- shifts are 0..63
    assert_error(() => shl(1, 63))                                      -- does not fit in 64 bits: error, never wraps
    assert_eq(hex(xor_bytes(bytes("3636", "hex"), bytes("5c5c", "hex"))), "0x6a6a")
    assert_error(() => xor_bytes(bytes("ab"), bytes("a")))              -- lengths must match

test "math operators: / is float, // floors like Python, ** binds tighter than unary minus"
    assert_eq(2 ** 10, 1024)
    assert_eq(10 % 3, 1)
    assert_eq(7 / 2, 3.5)
    assert_eq(7 // 2, 3)
    assert_eq(-7 // 2, -4)
    assert_eq(7 // 0.1, 69.0)                                                -- CPython's answer (0.1 is a bit more than 0.1)
    assert_eq(-2 ** 2, -4)
    assert_eq(2 ** 53 + 1 == 9007199254740992.0, false)                      -- int vs float compare exactly
    assert_eq(round(2.5), 2)                                                 -- ties to even
    assert_eq(round_to(2.675, 2), 2.67)                                      -- a float rounds its real binary value
    assert_eq(int("ff", base = 16), 255)
    assert_eq(int("abc", fallback = 7), 7)
    assert_error(() => int("ff", 16))                                        -- ambiguous: name base = or fallback =

test "decimals are exact at any size, and can be written with an exponent"
    assert_eq(0.1d + 0.2d, 0.3d)
    assert_eq(text(1d + 10 ** 30), "1000000000000000000000000000001")
    assert_eq(type_of(1d + 10 ** 30), "decimal")
    assert_eq(text(1d / 3d), "0.3333333333333333333333333333")               -- / rounds to 28 significant digits
    assert_eq(floor(2.5d), 2)
    assert_eq(round_to(1.005d, 2), 1.00d)                                    -- half to even, stays decimal
    assert_eq(round_to(2.675d, 2), 2.68d)
    assert_eq(decimal("1.5e-3"), 0.0015d)
    assert_eq(decimal("1e5"), 100000d)
    assert_eq(2e3d, 2000d)
    assert_eq(text(decimal("1.50e1")), "15.0")                               -- scale = mantissa's minus the exponent
    assert_error(() => decimal("1e999999999"))                               -- more than 4300 digits
    assert_error(() => 1d == 1.0)                                            -- decimal vs float: convert on purpose
    assert_eq(1d == 1, true)                                                 -- integers mix freely

test "reductions: nothing is skipped, NaN propagates, std/var are sample (ddof = 1)"
    assert_eq(mean([1, nothing, 3]), 2.0)
    assert_eq(median([3, nothing, 1]), 2.0)
    assert_eq(min([3, nothing, 1]), 1)
    assert(is_nan(sum([1, nan])))
    assert(is_nan(min([nan, 3])))
    assert(is_nan(median([1, nan, 3])))
    assert_eq(sum([]), 0)
    assert_eq(product([]), 1)
    assert_error(() => mean([]))
    assert_error(() => mean([nothing, nothing]))                             -- every value missing
    assert_eq(std([1, 2, 3, 4]), 1.2909944487358056)                         -- sample, like pandas / Excel STDEV.S
    assert_eq(var([1, 2, 3, 4]), 1.6666666666666667)
    assert_eq(std([1, 2, 3, 4], ddof = 0), 1.118033988749895)                -- population, numpy's default
    assert(is_nan(std([5])))                                                 -- fewer than ddof + 1 values
    assert_eq(sum(fill_nan([1, nan], 0)), 1)

test "reductions keep decimals exact"
    assert_eq(sum([0.1d, 0.2d]), 0.3d)
    assert_eq(type_of(mean([1.5d, 2.5d])), "decimal")
    assert_eq(median([1d, 2d, 4d, 8d]), 3d)
    assert_eq(var([1000000000000000000001d, 1000000000000000000003d]), 2d)   -- a float would give 0
    assert_eq(percentile([1d, 2d, 4d], 90), 3.6d)

test "percentile takes 0..100, quantile takes 0..1"
    assert_eq(percentile([1, 2, 3, 4], 50), 2.5)
    assert_eq(quantile([1, 2, 3, 4], 0.5), 2.5)
    assert_eq(percentile([1, 2, 3, 4], 25), 1.75)
    assert_error(() => quantile([1], 50))
    assert_error(() => percentile([1], 101))
    assert_eq(mode([1, 2, 2, nothing]), 2)

test "arrays: length is the first dimension, size counts elements; a 0-d array has no length"
    let m be array([[1, 2], [3, 4]])
    assert_eq(length(m), 2)
    assert_eq(size(m), 4)
    assert_eq(shape(m), [2, 2])
    assert_eq(ndim(m), 2)
    assert_eq(to_list(m[-1]), [3, 4])
    assert_eq(to_list(slice(m, -1)), [[3, 4]])
    assert_error(() => length(array(5)))
    assert_eq(size(array(5)), 1)
    assert_eq(to_list(identity(2)), [[1, 0], [0, 1]])

test "enumerate walks an array's first axis, like length and a[i] (engine v0.6.46+)"
    let v be array([10, 20, 30])
    assert_eq(enumerate(v), [{"index": 0, "item": 10.0}, {"index": 1, "item": 20.0}, {"index": 2, "item": 30.0}])
    let m be array([[1, 2], [3, 4]])
    let rows be enumerate(m)
    assert_eq(length(rows), length(m))
    assert_eq(rows[1].index, 1)
    assert_eq(to_list(rows[1].item), [3, 4])                                -- a 2-D array gives its rows
    assert_eq(shape(enumerate(zeros([2, 2, 3]))[0].item), [2, 3])
    assert_eq(enumerate(array([])), [])
    let seen be []
    each x in to_list(v)                                                    -- `each x in v` is an error: walk a list
        set seen to append(seen, x)
    assert_eq(seen, [10, 20, 30])
    assert_error(() => enumerate(array(5)))                                 -- 0-d: "it is a scalar and has no rows"

test "arrays: named axis, elementwise math, masks, combine, locate"
    let m be array([[1, 2], [3, 4]])
    assert_eq(to_list(sum(m, axis = 0)), [4, 6])
    assert_eq(to_list(mean(m, axis = -1)), [1.5, 3.5])
    assert_eq(to_list(std(m, axis = 0, ddof = 0)), [1, 1])
    assert_eq(to_list(sum(m, 1)), [3, 7])                                    -- positional axis still works on arrays
    assert_error(() => sum([1, 2], axis = 0))                                -- a list has one axis
    assert_eq(to_list(m * m), [[1, 4], [9, 16]])                             -- * is elementwise, not matmul
    assert_eq(to_list(m ** 2), [[1, 4], [9, 16]])
    assert_eq(to_list(m // 3), [[0, 0], [1, 1]])
    assert_eq(to_list(m % 3), [[1, 2], [0, 1]])
    assert_eq(to_list(sqrt(array([4, 9]))), [2, 3])
    assert_eq(to_list(floor(array([1.5, -1.5]))), [1, -2])
    assert_eq(to_list(apply(array([1, 2, 3]), (x) => x * 10)), [10, 20, 30])
    assert_eq(to_list(where(array([1, 5, 2, 8]), (x) => x > 2)), [5, 8])
    assert_eq(to_list(concat([array([1, 2]), array([3])])), [1, 2, 3])
    assert_eq(to_list(concat([array([[1], [2]]), array([[3], [4]])], axis = 1)), [[1, 3], [2, 4]])
    assert_eq(to_list(stack([array([1, 2]), array([3, 4])])), [[1, 2], [3, 4]])
    assert_eq(argmax([3, 9, 2]), 1)
    assert_eq(argmin([nothing, 3, 1]), 2)                                    -- nothing skipped, position kept
    assert_eq(to_list(argmax(m, axis = 1)), [1, 1])
    assert_eq(cumsum([1, nothing, 3]), [1, nothing, 4])
    assert_eq(diff([1, 4, 9]), [3, 5])

test "linear algebra: dot is for vectors, matmul for matrices"
    assert_eq(norm(array([3, 4])), 5)
    assert_eq(dot(array([1, 2, 3]), array([4, 5, 6])), 32)
    let m be array([[1, 2], [3, 4]])
    assert_error(() => dot(m, m))                                            -- error points to matmul
    assert_eq(to_list(matmul(m, identity(2))), [[1, 2], [3, 4]])
    assert_eq(det(array([[2, 0], [0, 3]])), 6)
    assert_eq(to_list(solve(array([[2, 0], [0, 4]]), array([2, 8]))), [1, 2])
    assert_error(() => inv(array([[1, 2], [2, 4]])))                         -- singular: an error, never NaN

test "statistics and fitting: corr, cov, lstsq, polyfit, polyval"
    assert_eq(cov([1, 2, 3], [1, 2, 3]), 1.0)                                -- sample, like std
    assert_eq(cov([1, 2, 3], [1, 2, 3], ddof = 0), 0.6666666666666666)
    assert_eq(corr([1, nothing, 3, 4], [2, 5, 6, 8]), 1.0)                   -- a pair with nothing is skipped
    let x be lstsq(array([[1, 0], [0, 1], [1, 1]]), array([1, 2, 3]))
    assert(abs(x[0] - 1) < 1e-9 and abs(x[1] - 2) < 1e-9)                    -- float round-off: compare with a tolerance
    let rank_def be lstsq(array([[1, 1], [2, 2]]), array([2, 4]))           -- rank-deficient: minimum-norm, like numpy
    assert(abs(rank_def[0] - 1) < 1e-9 and abs(rank_def[1] - 1) < 1e-9)
    let fit be polyfit([0, 1, 2, 3], [1, 3, 5, 7], 1)                        -- [slope, intercept], highest degree first
    assert(abs(fit[0] - 2) < 1e-9 and abs(fit[1] - 1) < 1e-9)
    assert_error(() => polyfit([1, 1, 1], [1, 2, 3], 1))                     -- not determined by the data
    assert_eq(polyval([2.0, 1.0], 10), 21.0)
    assert_eq(polyval([2.0, 1.0], [1, nothing, 3]), [3.0, nothing, 7.0])     -- a missing x stays missing
    assert_error(() => polyval([2.0, nothing], 1))                           -- a missing coefficient is an error
    assert_error(() => lstsq(array([[1, 0], [0, 1]]), [1, nothing]))

test "array constructors and inspection"
    assert_eq(to_list(zeros(2)), [0, 0])
    assert_eq(to_list(ones([2, 2])), [[1, 1], [1, 1]])
    assert_eq(to_list(full(2, 7)), [7, 7])
    assert_eq(to_list(arange(0, 3)), [0, 1, 2])
    assert_eq(to_list(arange(0, 1, 0.5)), [0, 0.5])
    assert_eq(to_list(linspace(0, 1, 3)), [0, 0.5, 1])
    let m be reshape(arange(0, 4), [2, 2])
    assert_eq(to_list(m), [[0, 1], [2, 3]])
    assert_eq(to_list(transpose(m)), [[0, 2], [1, 3]])
    assert_eq(to_list(flatten(m)), [0, 1, 2, 3])
    assert_eq(at(m, [1, 0]), 2)
    assert_eq(trace(m), 3)
    assert_eq(is_array(m), true)
    assert(contains(eig(array([[2, 0], [0, 3]])), "values"))
    assert_eq(sort(keys(svd(m))), ["s", "u", "vt"])
    assert_eq(argmax([1, 3, 3]), 1)                                          -- first on a tie
    assert_eq(to_list(stack([array([1, 2]), array([3, 4])], axis = 1)), [[1, 3], [2, 4]])
    assert_eq(is_integer(1), true)
    assert_eq(is_integer(1.0), false)
    assert_eq(sum(where([1, nan, 2], is_finite)), 3)
    assert_error(() => 1.5 in [1.5d])                                        -- decimal vs float, also inside `in`

Bytes§

bytes es binario crudo, distinto de text. bytes(...) y decode(...) son inversos:

bytes("Hi")                 -- UTF-8 bytes
bytes("4869", "hex")        -- decode hex → bytes
bytes("SGk=", "base64")     -- decode base64 → bytes
bytes("SGk", "base64url")   -- decode base64url (URL-safe -_, padding optional; JWT/tokens) → bytes
bytes("StV1DL6", "base58")  -- decode base58 (Bitcoin/Solana alphabet) → bytes
bytes("JBSWY3DP", "base32") -- decode base32 RFC 4648 (Algorand convention) → bytes
decode(b)                   -- bytes → text (UTF-8 strict)
decode(b, "hex")            -- bytes → hex text (also base64 / base64url / base58 / base32)
sha256(x)                   -- raw digest (bytes); hex via decode(sha256(x), "hex")
bytes_to_int(b)             -- big-endian bytes → exact non-negative integer (empty → 0)
int_to_bytes(n, size?)      -- integer → big-endian bytes: minimal, or zero-padded to size
int_to_bytes_le(n, size)    -- integer → little-endian bytes of exactly size (binary structs, e.g. Solana u32/u64 LE)

text(b) muestra una repr hex como bytes(48656c6c6f) — no decodifica. Un secret nunca se materializa a través de bytes(...). bytes_to_int es exacto a cualquier ancho (un valor de 32 bytes jamás pasa por float), que es lo que piden los enteros de firmas/protocolos — mirá 38-blockchain.

hmac(data, key, algo?) devuelve el MAC como bytes (antes era hmac_sha256, que devolvía texto hex y ahora es un alias deprecado): mostralo con hex(mac) ("0x…") o decode(mac, "hex") (hex pelado). algo es "sha256" (default) o "sha512"; SHA-1 se rechaza. data y key pueden ser texto, bytes (se usan crudos — motor v0.6.44+; antes los bytes se hasheaban como su forma impresa) o un secret; cualquier otra cosa es error. Para verificar una firma que te mandaron (hex o base64, como las mandan Stripe o GitHub), usá verify_hmac(data, signature, key, algo?), que compara en tiempo constante.

Criptografía — ECDH, HKDF, AES-GCM (engine v0.6.20+)§

Nombres y formas estándar (los de WebCrypto), nada exótico — lo justo para correr un acuerdo de claves y un canal con cifrado autenticado desde un programa Synsema.

require random                                            -- ecdh_keypair generates key material
let me be ecdh_keypair("P-256")                           -- {private: secret, public: bytes}; also "P-521"
let shared be ecdh_shared_secret(private of me, their_public, "P-256")   -- a secret (the raw X coordinate)
let key be hkdf_sha256(shared, salt, bytes("app v1"), 32) -- 32 bytes → AES-256; stays a secret when the IKM is one
let nonce be random_bytes(12)                             -- 12 bytes, never reused with the same key
let ct be aes_gcm_encrypt(key, nonce, plaintext, aad)     -- ciphertext ‖ 16-byte tag (aad optional)
aes_gcm_decrypt(key, nonce, ct, aad)                      -- the plaintext, or an error — never partial bytes

Matemática§

Operadores + - * / // % . / es siempre float; // es división entera hacia abajo, exacta para enteros de cualquier tamaño y redondeando como Python (-7 // 2 → -4); en floats // y % dan la respuesta de CPython calculada sobre los valores binarios reales (7 // 0.1 → 69.0). liga más fuerte que el menos unario: -2 2 es -4. Enteros y floats se comparan exactamente (2 53 + 1 == 9007199254740992.0 es false). Constantes: pi, tau, e, inf, nan. La torre numérica también tiene decimal y complex.

Funciones (puras, sin capability; reales o complex donde se indica):

Decimales exactos§

Un decimal (1.50d, decimal("1.50")) tiene precisión arbitraria y un tipo estable: una operación decimal siempre da un decimal, nunca un float.

0.1d + 0.2d == 0.3d          -- true, exactly
1d + 10 ** 30                -- 1000000000000000000000000000001 (a decimal)
1d / 3d                      -- 0.3333333333333333333333333333 — / rounds to 28 significant digits
decimal("1.5e-3")            -- 0.0015: an exponent is allowed (engine v0.6.30+)
2e3d                         -- 2000
round_to(2.675d, 2)          -- 2.68: decimals round in decimal, half to even

Reducciones — las reglas comunes§

Toda reducción (sum, product, mean, min, max, median, percentile, quantile, std, var, y los agregados de tabla sum_of, mean_of… de Análisis de datos) sigue las mismas reglas — el modelo de polars/SQL:

1. nothing es un dato faltante y se saltea: mean([1, nothing, 3]) → 2.0. Todos los valores faltantes → error; una lista vacía → error, salvo sum([]) → 0 y product([]) → 1. 2. NaN es un resultado inválido y se propaga, en cualquier posición: sum([1, nan]) → nan, min([nan, 3]) → nan, median de datos con un NaN → nan. Limpiá antes con fill_nan(xs, 0) o where(xs, is_finite). 3. Un eje con nombre en arrays: sum(m, axis = 0) (bajando por las columnas), mean(m, axis = -1) (los negativos cuentan desde el final). El posicional sum(m, 1) sigue andando en arrays; axis sobre una lista es un error. 4. std y var son estadísticos muestrales (ddof = 1, como pandas, polars, R y DESVEST.M de Excel): std([1, 2, 3, 4]) → 1.2909944487358056. La cifra poblacional (el default de numpy, DESVEST.P) es std(xs, ddof = 0) → 1.118033988749895. ddof va solo por nombre. Menos de ddof + 1 valores → nan. 5. Los decimales siguen siendo decimales, calculados exacto como el statistics de Python: sum([0.1d, 0.2d]) → 0.3, var([1000000000000000000001d, 1000000000000000000003d]) → 2 (un float da 0), percentile([1d, 2d, 4d], 90) → 3.6.

percentile(x, p) toma p en 0–100; quantile(x, q) toma q en 0–1 (quantile([1], 50) es un error que apunta a percentile). Los dos interpolan linealmente (el default de numpy) y aceptan axis = en arrays: percentile(x, 50) == quantile(x, 0.5) == median(x).

Arrays numéricos y álgebra lineal§

array([...]) construye un array float de n dimensiones. + - / // % son elemento a elemento con broadcasting — no es el producto matricial. Álgebra lineal (2D, vía faer):

norm(array([3, 4]))                          -- 5
dot(array([1, 2, 3]), array([4, 5, 6]))      -- 32
matmul(a, b)                                  -- matrix product
solve(A, b)   det(A)   inv(A)   eig(A)   svd(A)

Estadística y ajuste§