## [Go](//go.dev), 135 bytes

~~~go
package n
import."strconv"
func f(s string)byte{if _,e:=Atoi(s)
e==nil{return'i'}
if _,e:=ParseFloat(s,64)
e==nil{return'f'}
return'n'}
~~~

## [Go](//go.dev), 418 bytes

Some people have mentioned that this above code doesnt pass all test cases. I
realized, that the rules in the question dont actually match rules used by
programming languages on what qualifies as an integer or float. For example, the
rules say that `1e3` is not a float, but in reality it is:

~~~go
package main

import (
   "fmt"
   "strconv"
)

func main() {
   f, err := strconv.ParseFloat("1e3", 64)
   fmt.Println(f, err) // 1000 <nil>
}
~~~

So I made an edit to handle these rules:

~~~go
package n
import."strconv"
func f(s string)byte{if s[0]=='0'&&s[1]!='.'{return'n'}
if len(s)>=3&&s[:2]=="-0"&&s[2]!='.'{return'n'}
if s[0]=='-'{}else if s[0]>='0'&&s[0]<='9'{}else{return'n'}
if s[len(s)-1]>='0'&&s[len(s)-1]<='9'{}else{return'n'}
for _,r:=range s{if r=='-'{}else if r=='.'{}else if r>='0'&&r<='9'{}else{return'n'}}
if _,e:=Atoi(s);e==nil{return'i'}
if _,e:=ParseFloat(s,64);e==nil{return'f'}
return'n'}
~~~

Test:

~~~go
package n

import (
   "fmt"
   "testing"
)

type testType struct {
   in string
   out byte
}

var tests = []testType{
   {"+1.0", 'n'}, {"--1.0", 'n'}, {"-0", 'i'}, {"-0.0", 'f'}, {"-001.1", 'n'},
   {"-1e3", 'n'}, {"-2", 'i'}, {".1", 'n'}, {"00", 'n'}, {"042", 'n'},
   {"1.", 'n'}, {"1.0", 'f'}, {"1.0.0", 'n'}, {"1.00", 'f'}, {"1.0ec3", 'n'},
   {"123", 'i'}, {"1e+3", 'n'}, {"1e-3", 'n'}, {"1e3", 'n'}, {"42", 'i'},
   {"62727.0033", 'f'}, {"644c.33", 'n'}, {"NaN", 'n'},
}

func TestNumber(t *testing.T) {
   for _, test := range tests {
      out := f(test.in)
      if out != test.out {
         fmt.Printf("%v %c %c\n", test.in, test.out, out)
      }
   }
}
~~~