PHP 개발자들이 자주 겪는 문제 중 하나가 바로 형 변환이다. 사용자 입력값이나 API 응답값을 정수/실수로 변환할 때, 많은 개발자들이 단순히 괄호로 감싸거나(int) $value 형태로 변환한다. 다만 이런 방식이 정말 안전한지, 예상치 못한 값이 들어올 때 어떻게 처리되는지 정확히 아는 개발자는 드물다. 이번에는 intval()과 floatval()의 정확한 동작 원리, 왜 타입 캐스팅보다 안전한지, 그리고 실전 예제를 통해 완벽하게 정리해보겠다.
타입 캐스팅 vs intval() 함수의 차이:
| 입력값 | (int) 캐스팅 | intval() 함수 | 차이 |
|---|---|---|---|
| "123" | 123 | 123 | 같음 |
| "123abc" | 123 | 123 | 같음 |
| "abc123" | 0 | 0 | 같음 |
| "12.34" | 12 | 12 | 같음 |
| true | 1 | 1 | 같음 |
| null | 0 | 0 | 같음 |
표만 보면 둘이 거의 같아 보인다. 하지만 intval()은 base 파라미터를 지원해서 진법 변환도 가능하고, 명시적으로 형 변환 의도를 드러낼 수 있다는 점에서 코드 가독성이 좋다.
intval(mixed $value, int $base = 10): int
파라미터 설명:
- $value: 변환할 값 (문자열, 숫자, 불린 등 어떤 타입이든 가능)
- $base: 진법 (기본값 10진법. 2~36진법 지원)
반환값: 항상 정수(int) 타입. 실패하면 0 반환
floatval(mixed $value): float
반환값: 항상 실수(float) 타입. 실패하면 0.0 반환
✗ 잘못된 코드 - 형 캐스팅만 사용
<?php
// 사용자가 입력한 나이
$input_age = $_POST['age'] ?? '';
$age = (int)$input_age; // 형 캐스팅만 수행
// 문제: 입력값이 "30세" 또는 "30 years"라면?
echo "나이: " . $age; // "나이: 30" (의도치 않게 30으로 변환됨)
// 더 위험한 경우
$suspicious_input = "0x1F"; // 16진수?
$value = (int)$suspicious_input;
echo $value; // 0 (의도치 않게 0으로 처리됨)
?>
✓ 올바른 코드 - intval()과 유효성 검사 함께 사용
<?php
// 방법 1: 순수 정수 문자열 검증
$input_age = $_POST['age'] ?? '';
if (!ctype_digit($input_age)) {
die("나이는 숫자만 입력 가능합니다");
}
$age = intval($input_age);
echo "나이: " . $age; // 안전하게 정수만 처리
// 방법 2: 범위 검증까지 추가
$age = intval($input_age);
if ($age < 0 || $age > 150) {
die("유효하지 않은 나이 범위입니다");
}
echo "나이: " . $age;
?>
결과/설명: ctype_digit()으로 숫자만 입력되었는지 미리 확인한 후 intval()로 변환하면, 예상치 못한 문자열이 섞여 있어도 안전하게 처리할 수 있다.
✗ 잘못된 코드 - 캐스팅만 하면 위험
<?php
// 외부 API에서 받은 JSON 응답
$json = '{"price": "19.99", "quantity": "5 units"}';
$data = json_decode($json, true);
// 단순 캐스팅
$price = (int)$data['price'];
$quantity = (int)$data['quantity'];
echo "가격: " . $price; // "가격: 19" (소수점이 버려짐)
echo "수량: " . $quantity; // "수량: 5" (뒤의 " units" 무시)
// 계산 오류 발생 가능
$total = $price * $quantity; // 95 (정확한 가격은 99.95여야 함)
?>
✓ 올바른 코드 - floatval()과 intval() 구분해서 사용
<?php
$json = '{"price": "19.99", "quantity": "5 units"}';
$data = json_decode($json, true);
// 가격은 floatval()로 실수 처리
$price = floatval($data['price']);
// 수량은 intval()로 정수 처리
$quantity = intval($data['quantity']);
echo "가격: " . $price; // "가격: 19.99"
echo "수량: " . $quantity; // "수량: 5"
// 정확한 계산
$total = $price * $quantity; // 99.95
echo "합계: " . $total; // "합계: 99.95"
?>
결과/설명: 가격처럼 소수점이 중요한 값은 floatval()으로, 수량처럼 정수만 필요한 값은 intval()로 구분해서 처리하면 계산 오류를 방지할 수 있다.
✗ 잘못된 코드 - 16진수를 그냥 정수로 변환
<?php
$hex_color = "FF"; // 16진수 색상 값
$decimal_value = intval($hex_color); // base 지정 없음
echo $decimal_value; // 0 ("FF"는 10진법으로 숫자가 아니므로 0)
?>
✓ 올바른 코드 - base 파라미터로 16진수 지정
<?php
$hex_color = "FF"; // 16진수 색상 값
$decimal_value = intval($hex_color, 16); // base=16 (16진법)
echo $decimal_value; // 255 (FF를 10진수로 변환)
// 다양한 진법 예제
echo intval("1010", 2); // 10 (2진수)
echo intval("77", 8); // 63 (8진수)
echo intval("ZZ", 36); // 1295 (36진수, 0-9와 a-z 사용)
?>
✗ 잘못된 것
<?php
$money = "99.99";
$quantity = 5;
// 실수: 둘 다 intval()로 변환
$money_int = intval($money); // 99 (소수점 버려짐)
$total = $money_int * $quantity; // 495 (정확한 값은 499.95)
?>
✓ 올바른 것
<?php
$money = "99.99";
$quantity = 5;
// 금액은 floatval(), 수량은 intval()
$money_float = floatval($money); // 99.99
$total = $money_float * $quantity; // 499.95
?>
✗ 잘못된 것
<?php
$value = "not_a_number";
$result = intval($value);
// 0이 반환되지만, 의도적으로 0을 입력한 것과 구분 불가
if ($result === 0) {
// 실제 0인지, 변환 실패인지 알 수 없음
}
?>
✓ 올바른 것
<?php
$value = "not_a_number";
// is_numeric()으로 변환 가능 여부를 먼저 확인
if (is_numeric($value)) {
$result = intval($value);
} else {
die("숫자 형식이 아닙니다");
}
?>
✗ 잘못된 것
<?php
// base는 2~36 사이만 가능
$result = intval("10", 50); // Warning: intval() expects base to be between 2 and 36
?>
✓ 올바른 것
<?php
// 진법 유효성 검사
$base = 16; // 사용자 입력
if ($base < 2 || $base > 36) {
die("base는 2~36 사이여야 합니다");
}
$result = intval("FF", $base); // 안전하게 처리
?>
프로젝트에서 반복해서 사용할 수 있는 안전한 형 변환 함수:
<?php
/**
* 안전한 정수 변환 함수
*/
function safe_intval($value, $base = 10, $min = null, $max = null) {
// base 유효성 검사
if ($base < 2 || $base > 36) {
throw new InvalidArgumentException("base는 2~36 사이여야 합니다");
}
// 숫자 여부 확인
if (!is_numeric($value)) {
return 0; // 또는 null, false 반환 가능
}
$int_value = intval($value, $base);
// 범위 검증
if ($min !== null && $int_value < $min) {
return $min;
}
if ($max !== null && $int_value > $max) {
return $max;
}
return $int_value;
}
/**
* 안전한 실수 변환 함수
*/
function safe_floatval($value, $min = null, $max = null, $decimals = 2) {
if (!is_numeric($value)) {
return 0.0;
}
$float_value = floatval($value);
// 범위 검증
if ($min !== null && $float_value < $min) {
return $min;
}
if ($max !== null && $float_value > $max) {
return $max;
}
// 소수점 자리 반올림
return round($float_value, $decimals);
}
// 사용 예제
$age = safe_intval($_POST['age'] ?? '', 10, 0, 150);
$price = safe_floatval($_POST['price'] ?? '', 0, 1000000, 2);
?>
intval()과 floatval()은 단순한 형 변환 함수처럼 보이지만, PHP의 동적 타입 시스템 속에서 명시적이고 안전한 데이터 처리를 위한 필수 도구다. 대부분의 개발자들은 (int)나 (float) 캐스팅에 익숙해서 이 함수들의 가치를 간과하지만, base 파라미터 지원, 명확한 의도 표현, 그리고 is_numeric()과의 조합을 통한 사전 검증으로 훨씬 견고한 코드를 작성할 수 있다. 사용자 입력값, API 응답값, 데이터베이스 조회 결과 등 외부에서 들어오는 모든 값은 intval()/floatval() + 사전 검증을 습관화하면, 예상치 못한 타입 오류로 인한 버그를 크게 줄일 수 있을 것이다.