@@ -3627,13 +3627,28 @@ static int unary_validate_numeric_string(const char *str, const char *which) {
36273627 return -1 ;
36283628 }
36293629 const char * p = unary_skip_sign_ws (str );
3630- /* Accept inf / nan tokens (case-insensitive, with optional trailing
3631- junk consistent with strtod's contract). */
3630+ /* Accept inf / infinity / nan tokens (case-insensitive). The token
3631+ must consume the rest of the (trimmed) literal — trailing junk
3632+ such as "infX" / "nanZ" is rejected rather than silently read as
3633+ a valid prefix the way strtod would, mirroring the strict array-
3634+ input inferrer `ndarray_infer_dtype_from_string`. */
36323635 char low3 [4 ] = {0 };
36333636 for (int i = 0 ; i < 3 && p [i ]; i ++ ) {
36343637 low3 [i ] = (char )(p [i ] | 0x20 );
36353638 }
36363639 if (!strncmp (low3 , "inf" , 3 ) || !strncmp (low3 , "nan" , 3 )) {
3640+ const char * t = p + 3 ;
3641+ if (low3 [0 ] == 'i' ) { /* maybe the "infinity" spelling */
3642+ char low5 [6 ] = {0 };
3643+ for (int i = 0 ; i < 5 && t [i ]; i ++ ) low5 [i ] = (char )(t [i ] | 0x20 );
3644+ if (!strncmp (low5 , "inity" , 5 )) t += 5 ;
3645+ }
3646+ while (* t == ' ' || * t == '\t' || * t == '\n' || * t == '\r' ) t ++ ;
3647+ if (* t != '\0' ) {
3648+ zend_throw_error (NULL ,
3649+ "NDArray clip: '%s' is not a valid number: %s." , which , str );
3650+ return -1 ;
3651+ }
36373652 return 0 ;
36383653 }
36393654 int saw_digit = 0 ;
@@ -3670,12 +3685,65 @@ static int unary_validate_numeric_string(const char *str, const char *which) {
36703685/**
36713686 * @brief Skip leading ASCII whitespace, returning the first non-space char's
36723687 * pointer. Mirrors `strtoll`'s leading-whitespace handling.
3688+ *
3689+ * @param[in] s NUL-terminated string to scan.
3690+ * @return Pointer into @p s at the first non-whitespace character (the
3691+ * terminating NUL when @p s is empty or all whitespace).
36733692 */
36743693static inline const char * unary_skip_ws (const char * s ) {
36753694 while (* s == ' ' || * s == '\t' || * s == '\n' || * s == '\r' ) s ++ ;
36763695 return s ;
36773696}
36783697
3698+ /** Special-value kind of a validated clip-bound literal. */
3699+ typedef enum {
3700+ UNARY_FINITE = 0 , UNARY_POS_INF , UNARY_NEG_INF , UNARY_NAN
3701+ } unary_special_t ;
3702+
3703+ /**
3704+ * @brief Classify an already-validated clip-bound literal as finite, ±inf,
3705+ * or nan (case-insensitive, honouring an optional leading sign).
3706+ *
3707+ * @param[in] str NUL-terminated, syntactically validated literal.
3708+ * @return The special-value kind; `UNARY_FINITE` for an ordinary number.
3709+ */
3710+ static unary_special_t unary_classify_special (const char * str ) {
3711+ const char * p = unary_skip_ws (str );
3712+ int neg = 0 ;
3713+ if (* p == '+' || * p == '-' ) { neg = (* p == '-' ); p ++ ; }
3714+ char low3 [4 ] = {0 };
3715+ for (int i = 0 ; i < 3 && p [i ]; i ++ ) low3 [i ] = (char )(p [i ] | 0x20 );
3716+ if (!strncmp (low3 , "inf" , 3 )) return neg ? UNARY_NEG_INF : UNARY_POS_INF ;
3717+ if (!strncmp (low3 , "nan" , 3 )) return UNARY_NAN ;
3718+ return UNARY_FINITE ;
3719+ }
3720+
3721+ /**
3722+ * @brief Write the representable extreme of an integer dtype into @p out_buf.
3723+ *
3724+ * Used to give an inf/nan clip bound PyTorch's "no bound" semantics on the
3725+ * 8 integer dtypes (strtoll/strtoull would otherwise read zero digits from
3726+ * the token and yield 0, collapsing the clip range).
3727+ *
3728+ * @param[in] dt Canonical dtype string.
3729+ * @param[in] want_max Non-zero → dtype maximum; zero → dtype minimum
3730+ * (0 for the unsigned dtypes).
3731+ * @param[out] out_buf Buffer of `elsize(dt)` bytes to receive the value.
3732+ * @return 1 when @p dt is one of the 8 integer dtypes (value written);
3733+ * 0 otherwise (a float dtype — caller handles it).
3734+ */
3735+ static int unary_write_int_extreme (const char * dt , int want_max , void * out_buf ) {
3736+ if (!strcmp (dt , "int8" )) { * (int8_t * )out_buf = want_max ? INT8_MAX : INT8_MIN ; return 1 ; }
3737+ if (!strcmp (dt , "int16" )) { * (int16_t * )out_buf = want_max ? INT16_MAX : INT16_MIN ; return 1 ; }
3738+ if (!strcmp (dt , "int32" )) { * (int32_t * )out_buf = want_max ? INT32_MAX : INT32_MIN ; return 1 ; }
3739+ if (!strcmp (dt , "int64" )) { * (int64_t * )out_buf = want_max ? INT64_MAX : INT64_MIN ; return 1 ; }
3740+ if (!strcmp (dt , "uint8" )) { * (uint8_t * )out_buf = want_max ? UINT8_MAX : 0 ; return 1 ; }
3741+ if (!strcmp (dt , "uint16" )) { * (uint16_t * )out_buf = want_max ? UINT16_MAX : 0 ; return 1 ; }
3742+ if (!strcmp (dt , "uint32" )) { * (uint32_t * )out_buf = want_max ? UINT32_MAX : 0 ; return 1 ; }
3743+ if (!strcmp (dt , "uint64" )) { * (uint64_t * )out_buf = want_max ? UINT64_MAX : 0 ; return 1 ; }
3744+ return 0 ;
3745+ }
3746+
36793747/**
36803748 * @brief Parse @p str into the typed scalar buffer @p out_buf for @p dt.
36813749 *
@@ -3703,6 +3771,21 @@ static int unary_parse_typed_scalar(const char *dt, const char *str,
37033771 const char * which , void * out_buf ) {
37043772 if (unary_validate_numeric_string (str , which ) < 0 ) return -1 ;
37053773
3774+ /* inf / nan bounds on integer dtypes: strtoll/strtoull read zero
3775+ digits from the token and yield 0, which would collapse the clip
3776+ range. Map the token to the dtype's representable extreme so an
3777+ inf bound acts as PyTorch's "no bound" (−inf → MIN, +inf → MAX),
3778+ and a nan bound becomes the no-op extreme for whichever side it
3779+ sits on (min → MIN, max → MAX), matching how the float path
3780+ silently ignores a nan bound. Float dtypes fall through so strtod
3781+ yields a real ±inf / nan. */
3782+ unary_special_t sp = unary_classify_special (str );
3783+ if (sp != UNARY_FINITE ) {
3784+ int want_max = (sp == UNARY_POS_INF ) ||
3785+ (sp == UNARY_NAN && !strcmp (which , "max" ));
3786+ if (unary_write_int_extreme (dt , want_max , out_buf )) return 0 ;
3787+ }
3788+
37063789 /* Narrow integer dtypes — saturate the bound to the dtype range so
37073790 out-of-range literals don't wrap via the implicit `(T)strtoll(...)`
37083791 cast inside `ndarray_set_from_string`. int64/uint64 keep the
@@ -3713,39 +3796,39 @@ static int unary_parse_typed_scalar(const char *dt, const char *str,
37133796 if (!strcmp (dt , "uint8" )) {
37143797 if (is_neg ) { * (uint8_t * )out_buf = 0 ; return 0 ; }
37153798 unsigned long long v = strtoull (p , NULL , 10 );
3716- * (uint8_t * )out_buf = (uint8_t )(v > 0xFFu ? 0xFFu : v );
3799+ * (uint8_t * )out_buf = (uint8_t )(v > UINT8_MAX ? UINT8_MAX : v );
37173800 return 0 ;
37183801 }
37193802 if (!strcmp (dt , "uint16" )) {
37203803 if (is_neg ) { * (uint16_t * )out_buf = 0 ; return 0 ; }
37213804 unsigned long long v = strtoull (p , NULL , 10 );
3722- * (uint16_t * )out_buf = (uint16_t )(v > 0xFFFFu ? 0xFFFFu : v );
3805+ * (uint16_t * )out_buf = (uint16_t )(v > UINT16_MAX ? UINT16_MAX : v );
37233806 return 0 ;
37243807 }
37253808 if (!strcmp (dt , "uint32" )) {
37263809 if (is_neg ) { * (uint32_t * )out_buf = 0 ; return 0 ; }
37273810 unsigned long long v = strtoull (p , NULL , 10 );
3728- * (uint32_t * )out_buf = (uint32_t )(v > 0xFFFFFFFFu ? 0xFFFFFFFFu : v );
3811+ * (uint32_t * )out_buf = (uint32_t )(v > UINT32_MAX ? UINT32_MAX : v );
37293812 return 0 ;
37303813 }
37313814 if (!strcmp (dt , "int8" )) {
37323815 long long v = strtoll (str , NULL , 10 );
3733- if (v > 0x7F ) v = 0x7F ;
3734- else if (v < -0x80 ) v = -0x80 ;
3816+ if (v > INT8_MAX ) v = INT8_MAX ;
3817+ else if (v < INT8_MIN ) v = INT8_MIN ;
37353818 * (int8_t * )out_buf = (int8_t )v ;
37363819 return 0 ;
37373820 }
37383821 if (!strcmp (dt , "int16" )) {
37393822 long long v = strtoll (str , NULL , 10 );
3740- if (v > 0x7FFF ) v = 0x7FFF ;
3741- else if (v < -0x8000 ) v = -0x8000 ;
3823+ if (v > INT16_MAX ) v = INT16_MAX ;
3824+ else if (v < INT16_MIN ) v = INT16_MIN ;
37423825 * (int16_t * )out_buf = (int16_t )v ;
37433826 return 0 ;
37443827 }
37453828 if (!strcmp (dt , "int32" )) {
37463829 long long v = strtoll (str , NULL , 10 );
3747- if (v > 0x7FFFFFFFLL ) v = 0x7FFFFFFFLL ;
3748- else if (v < -0x80000000LL ) v = -0x80000000LL ;
3830+ if (v > INT32_MAX ) v = INT32_MAX ;
3831+ else if (v < INT32_MIN ) v = INT32_MIN ;
37493832 * (int32_t * )out_buf = (int32_t )v ;
37503833 return 0 ;
37513834 }
0 commit comments