1. Use bash parameter expansion:
string="2278"
string_to_insert="."
index_position=3
result=${string::-index_position}$string_to_insert${string: -index_position}
echo $result
2.278
... Or even:
if (( index_position <= ${#string} )); then
    result=${string::-index_position}$string_to_insert${string: -index_position}
    echo $result
else
    echo "Error string '$string' too small"
fi
More informations about this in the bash's man page, in Parameter Expansion section search for "Substring Expansion":
man -Pless\ '+/^\ *Parameter\ *Expansion\|Substring\ *Expansion' bash
1.1 As a function
If you want this as a function, regarding the first link shown in you request which stand for same request, but from start of string instead of end, I would suggest to simply use same convention than used in bash, where using negative position mean from end of string, so we just have to write one function for both operations:
insertAt() { 
    if [[ $1 == -v ]]; then
        local -n __iAt_result=$2
        local __iAt_setVar=true
        shift 2
    else
        local __iAt_result __iAt_setVar=false
    fi
    (( $# != 3 )) &&
        printf 'Usage: %s [-v <varname>] <source string> <string to insert> <[-]position>\n' \
          "${FUNCNAME[0]}" >&2 &&
        return 1
    case ${3#-} in *[^0-9]* )
        echo 'Error: 3rd argument must be an integer!' >&2
        return 1 ;;
    esac
    local __iAt_string=$1 __iAt_toInsert=$2 __iAt_position=$3
    (( ${__iAt_position#-} > ${#__iAt_string} )) &&
        echo 'Error: position out of string length' >&2 &&
        return 1
    printf -v __iAt_result '%s%s%s' "${__iAt_string::__iAt_position}" \
          "$__iAt_toInsert" "${__iAt_string: __iAt_position}"
    $__iAt_setVar || echo "$__iAt_result"
}
insertAt 314159265 . 1
3.14159265
string="Hell!"
insertAt -v string "$string" 'o world' -1
echo "$string"
Hello world!
insertAt 314159265 1 .
Error: 3rd argument must be an integer!
Then if you try:
for i in {-5..5};do
    unset result
    insertAt -v result 2283 . $i
    [[ -v result ]] && printf '%5d: %s\n' $i "$result"
done
You will see:
Error: position out of string length
   -4: .2283
   -3: 2.283
   -2: 22.83
   -1: 228.3
    0: .2283
    1: 2.283
    2: 22.83
    3: 228.3
    4: 2283.
Error: position out of string length
2. Same, but for POSIX shell:
In POSIX sh, string indexing is undefined.
So for doing this we have to find another way.
2.1 POSIX shell  way using sed:
Using only one fork to sed:
insertAt() {
    if [ "$1" = "-v" ]; then
        __iAt_setVar=true __iAt_result=$2
        shift 2
    else
        __iAt_setVar=false __iAt_result=__iAt_result
    fi
    [ $# -ne 3 ] && echo >&2 \
        'Usage: insertAt [-v <varname>] <source string> <string to insert> <[-]position>' &&
        return 1
    case ${3#-} in
        *[!0-9]*|'' )
            echo 'Error: 3rd argument must be an integer!' >&2
            return 1 ;;
    esac
    [ "${3#-}" -gt ${#1} ] &&
       echo 'Error: position out of string length' >&2 &&
       return 1
    case $3 in
        -* )
            read -r "${__iAt_result?}" <<EOInLine__IAt_Result
$(echo "$1" | sed -e "s/.\{${3#-}\}$/${2}&/")
EOInLine__IAt_Result
            ;;
        * )
            read -r "${__iAt_result?}" <<EOInLine__IAt_Result
$(echo "$1" | sed -e "s/^.\{$3\}/&${2}/")
EOInLine__IAt_Result
            ;;
    esac
    if ! $__iAt_setVar; then echo "$__iAt_result"; fi
}
Will work exactly same:
insertAt 314159265 . 1
3.14159265
string="Hell!"
insertAt -v string "$string" 'o world' -1
echo "$string"
Hello world!
insertAt 314159265 1 .
Error: 3rd argument must be an integer!
for i in $(seq -- -5 5); do
    unset result
    insertAt -v result 2283 . "$i"
    [ -n "$result" ] && printf '%5d: %s\n' "$i" "$result"
done
Error: position out of string length
   -4: .2283
   -3: 2.283
   -2: 22.83
   -1: 228.3
    0: .2283
    1: 2.283
    2: 22.83
    3: 228.3
    4: 2283.
Error: position out of string length
2.2 Pure shell without fork
But with loops! Suitable for small strings as cited in you sample.
Under posix shell, we could use either Remove matching prefix pattern or Remove matching suffix pattern,
depending on which operation (from start or from end) are to be done.
A little condensed for quick display here, on SO:
insertAt() {
    if [ "$1" = "-v" ]; then __iAt_setVar=true __iAt_result=$2; shift 2
    else __iAt_setVar=false __iAt_result=__iAt_result; fi
    [ $# -ne 3 ] && echo >&2 \
        'Usage: insertAt [-v <varname>] <source string> <string to insert> <[-]position>' &&
        return 1
    case ${3#-} in *[!0-9]*|'' )
        echo 'Error: 3rd argument must be an integer!' >&2
        return 1 ;; esac
    [ "${3#-}" -gt ${#1} ] &&
       echo 'Error: position out of string length' >&2 &&
       return 1
    __iAt_var1="" __iAt_var2="" __iAt_cnt=$(( ${#1} - ${3#-} ))
    while [ $__iAt_cnt -gt 0 ]; do
           __iAt_var1="?$__iAt_var1" __iAt_cnt=$((__iAt_cnt-1))
    done
    __iAt_cnt=$(( ${3#-} ))
    while [ $__iAt_cnt -gt 0 ]; do
           __iAt_var2="?$__iAt_var2" __iAt_cnt=$((__iAt_cnt-1))
    done
    case $3 in -* ) 
            read -r "${__iAt_result?}" <<EOInLine__IAt_Result
${1%$__iAt_var2}$2${1#$__iAt_var1}
EOInLine__IAt_Result
            ;; * )
            read -r "${__iAt_result?}" <<EOInLine__IAt_Result
${1%$__iAt_var1}$2${1#$__iAt_var2}
EOInLine__IAt_Result
            ;; esac
    if ! $__iAt_setVar; then echo "$__iAt_result"; fi
}
Then again:
insertAt 314159265 . 1
3.14159265
string="Hell!"
insertAt -v string "$string" 'o world' -1
echo "$string"
Hello world!
insertAt 314159265 1 .
Error: 3rd argument must be an integer!
for i in $(seq -- -5 5); do
    unset result
    insertAt -v result 2283 . "$i"
    [ -n "$result" ] && printf '%5d: %s\n' "$i" "$result"
done
Error: position out of string length
   -4: .2283
   -3: 2.283
   -2: 22.83
   -1: 228.3
    0: .2283
    1: 2.283
    2: 22.83
    3: 228.3
    4: 2283.
Error: position out of string length
     
    
echo ${string::-index_position}$string_to_insert${string: -index_position}