data; return self::parseNumber((string)$v, false); } /** 整数(CAST AS INTEGER) */ public static function toInt(mixed $v): ?int { $n = self::toNumber($v); if ($n === null) return null; if (is_float($n)) { if (is_nan($n)) return 0; if ($n >= 9.2233720368547758e18) return PHP_INT_MAX; if ($n <= -9.2233720368547758e18) return PHP_INT_MIN; return (int)$n; } return $n; } /** 文字列にする(数値はSQLiteと同じ形) */ public static function toText(mixed $v): ?string { if ($v === null) return null; if (is_int($v)) return (string)$v; if (is_float($v)) return self::realToText($v); if ($v instanceof Blob) return $v->data; return (string)$v; } /** REALの文字列(%!.15gと同じ) */ public static function realToText(float $f): string { if (is_nan($f)) return 'NaN'; if (is_infinite($f)) return $f > 0 ? 'Inf' : '-Inf'; if ($f == 0.0) return '0.0'; $e = sprintf('%.14e', $f); [$mant, $exp] = explode('e', $e); $exp = (int)$exp; if ($exp < -4 || $exp >= 15) { $mant = rtrim(rtrim($mant, '0'), '.'); if (!str_contains($mant, '.')) $mant .= '.0'; return $mant.'e'.($exp < 0 ? '-' : '+').str_pad((string)abs($exp), 2, '0', STR_PAD_LEFT); } $s = sprintf('%.'.max(0, 14 - $exp).'F', $f); if (str_contains($s, '.')) $s = rtrim(rtrim($s, '0'), '.'); if (!str_contains($s, '.')) $s .= '.0'; return $s; } /** * 真偽(NULLはnull) */ public static function truth(mixed $v): ?bool { if ($v === null) return null; if (is_int($v)) return $v !== 0; if (is_float($v)) return $v != 0.0; $n = self::toNumber($v); return $n != 0; } /** * 比べる前の親和性(SQLiteの比較の規則) * * @param array $l [値, 親和性] * @param array $r [値, 親和性] * @return array [左の値, 右の値] */ public static function prepareCompare(mixed $a, string $affA, mixed $b, string $affB): array { $numA = $affA === self::INTEGER || $affA === self::REAL || $affA === self::NUMERIC; $numB = $affB === self::INTEGER || $affB === self::REAL || $affB === self::NUMERIC; if ($numA && !$numB) { if (is_string($b)) $b = self::apply($b, self::NUMERIC); } else if ($numB && !$numA) { if (is_string($a)) $a = self::apply($a, self::NUMERIC); } else if ($affA === self::TEXT && $affB === '') { if (is_int($b) || is_float($b)) $b = self::toText($b); } else if ($affB === self::TEXT && $affA === '') { if (is_int($a) || is_float($a)) $a = self::toText($a); } return [$a, $b]; } /** SQLのリテラル(quote()) */ public static function quote(mixed $v): string { if ($v === null) return 'NULL'; if (is_int($v)) return (string)$v; if (is_float($v)) return self::realToText($v); if ($v instanceof Blob) return "X'".strtoupper(bin2hex($v->data))."'"; return "'".str_replace("'", "''", (string)$v)."'"; } /** typeof() */ public static function type(mixed $v): string { if ($v === null) return 'null'; if (is_int($v)) return 'integer'; if (is_float($v)) return 'real'; if ($v instanceof Blob) return 'blob'; return 'text'; } /** GROUP BY・DISTINCT用のキー */ public static function key(mixed $v, string $coll = 'BINARY'): string { if ($v === null) return 'n'; if (is_int($v)) return 'i'.$v; if (is_float($v)) { if (is_finite($v) && floor($v) === $v && abs($v) < 9.2e18) return 'i'.(int)$v; return 'f'.$v; } if ($v instanceof Blob) return 'b'.strlen($v->data).':'.$v->data; $s = (string)$v; if ($coll === 'NOCASE') $s = strtolower($s); else if ($coll === 'RTRIM') $s = rtrim($s, ' '); return 's'.strlen($s).':'.$s; } }