Interpolation
I propose using Interpolation.
list = Prime ~Array~ 3000; intf = Interpolation[ {list, Range@Length@list}\[Transpose], InterpolationOrder -> 0 ];
Then, for point x:
x = 12225.4; Which[ x < First@list , {-∞, First@list}, x > Last@list , {Last@list, ∞}, True , list[[#-1 ;; #]]& @ intf @ x ]
{12211, 12227}
This could all be done inside Interpolation as well:
intf2 = Interpolation[ Join[ {{First@list, {-∞, 2}}}, Thread[{Rest@list, Partition[list, 2, 1]}], {{Last@list + 1, {Last@list, ∞}}} ], InterpolationOrder -> 0 ]; intf2[12225.4]
{12211, 12227}
Ordering
The method above was written from the perspective of repeated searching within the same list, and as noted in the comments it is assumed that the input list is sorted and free of duplicates.
If these assumptions do not hold other methods become appealing. After a review of others answers seeking inspiration, including those by kglr, celtschk and Leonid, I find Leonid's use of UnitStep to have great promise but his function is hobbled by the comparatively slow function Position. We can replace it with a use of Ordering.
This function requires a sorted list as input, but including the overhead of Sort I still find it faster than other methods I tried such as a separate application of Ordering in an earlier revision of this answer.
I use an explicit Subtract for performance.
Code:
seekOrdered[x_, list_] /; x < First @ list := {-∞, First @ list} seekOrdered[x_, list_] /; x >= Last @ list := {Last @ list, ∞} seekOrdered[x_, list_] := list[[# ;; # + 1]] & @@ Ordering[UnitStep @ Subtract[x, list], -1]
Here are comparative timings including Leonid's getInterval, celtschk's function, and a variation of kglr's interval2 using Replace rather than ReplaceList to return a single interval (in the case of ambiguous matches) for somewhat better performance. (Credit to Ali Hashmi for noting this.)
The various functions I am comparing take slightly different interpretations of the end point behavior requested therefore output does not precisely match. It should be possible to change the behavior of my function with a bit of tinkering should that be required for a particular application.
The other functions as I will be timing them:
getInterval[ints_List, num_] := Position[UnitStep[ints - num], 1, 1, 1] /. {{{1}} -> {-Infinity, First@ints}, {} -> {Last@ints, Infinity}, {{n_}} :> ints[[n - 1 ;; n]]}; celtsF[x_, list_List] := Module[{pos = Last@Ordering@Ordering[Append[list, x]]}, Which[pos == 1, {-Infinity, First@list}, pos == Length[list] + 1, {Last@list, Infinity}, True, list[[{pos - 1, pos}]]]] interval2fast[x_, list_List] := Replace[#, {___, a_, x, b_, ___} :> {a, b}] &@ Join[{-Infinity}, Sort[Join[list, {x}]], {Infinity}]
Benchmark 1:
list = Prime ~Array~ 3000; xs = RandomInteger[{-100, 30000}, 5000]; interval2fast[#, list] & /@ xs; // RepeatedTiming // First getInterval[list, #] & /@ xs; // RepeatedTiming // First celtsF[#, list] & /@ xs; // RepeatedTiming // First seekOrdered[#, list] & /@ xs; // RepeatedTiming // First
1.25
0.5890
0.224
0.114
With a packed input list:
list = Developer`ToPackedArray @ list; (* other code the same *)
1.16
0.5370
0.129
0.0689
With Reals rather than Integers for the search elements:
xs = RandomReal[{-100, 30000}, 5000]; (* other code the same *)
1.54
0.5674
0.855
0.0981
With Reals rather than Integers for the list:
list = Sort @ RandomReal[27000, 3000]; (* other code the same *)
1.88
0.552
0.129
0.0895
Of course for this repeated application Interpolation is faster still:
intf2 /@ xs; // RepeatedTiming // First // Quiet
0.0112
fifxis an element oflist? $\endgroup${{-∞,x},{x,∞}}$\endgroup$