It is not a problem with numbers in general, it is a problem with primitives.
It is exceptionally difficult to explain this to non-programmers because it gets into certain programming concepts for which there is no easily available metaphor or simile in real life to use as a basis of explanation.
Pictures drawn on a white board would help a lot too.
So I’m going to have to resort to “because I said so”. But I’ll give it a try.
In Java and the Rules DSL there are two basic types, Objects and Primitives. Whenever you see a type after a var or a val that starts with an uppercase letter (e.g. val String, var Number, etc.) you are dealing with an Object. If it starts with a lowercase letter (e.g. var int, val boolean, etc.) you are dealing with a primitive. If you do not specify the type you can usually assume that it is an Object.
The big difference for this discussion between Objects and Primitives is that Objects have methods where Primitives do not.
In Java and the Rules DSL there is a concept of Inheritance. This only applies to Objects. Inheritance allows one to take an existing Object type, called a Class, and add to it to make it into something different. This something difference becomes a Child of the original Class. The top level base Class for all Objects in Java and the Rules DSL is called Object. The Child can be treated as if it were the parent because everything the parent can do the child can do too.
In addition to some other useful things, Object implements a method called toString. And since Object is the parent of all Objects, that means all Classes also implement a toString method.
But primitives do not inherit from Object. They don’t inherit from anything and they don’t have any methods at all which includes toString.
So, because all Objects have a toString method, the language allows one to use an Object in a place where a String is required and the language is smart enough to say “Hey, that’s an Object but I need a String so I’ll call toString because I know all Objects have a toString method.” But when you have a Primitive there is no toString method so almost all of the time the language has no idea how to convert the primitive value to a String.
Now we need to look at the sendCommand Action. This Action only supports two arguments: String, String. This is because the action needs to be completely generic and work for all Item types.
There is also a sendCommand method on the Items. In this case the sendCommand method is defined by the class for that Item which allows the class to create sendCommand methods that take different types of data. For example, the NumberItem class would have sendCommand(int), sendCommand(long), sendCommand(float), sendCommand(double), sendCommand(Number), sendCommand(DecimalType), and sendCommand(String) methods. Each of these separate methods can be individually written to handle all of these different types of Objects and primitives unlike the Action which can only handle Strings.
So if I have a Number Item named MyItem:
val DecimalType zero = 0 // Object DecimalType
val Number one = 1 // Object Number
val two = 2 // When a type isn't provided for numerical values the Object BigDecimal is used
val int three = 3 // Primitive int
val String four = "4" // Object String
sendCommand(MyItem, zero) // Success: MyItem.toString and zero.toString is called to get the String values
sendCommand(MyItem, one) // Success: MyItem.toString and one.toString is called to get the String values
sendCommand(MyItem, two) // Success: MyItem.toString and two.toString is called to get the String values
sendCommand(MyItem, three) // Failure: three is a primitive and has no toString method
sendCommand(MyItem, four) // Success: MyItem.toString is called and four is already a String
MyItem.sendCommand(one) // Success: MyItem.sendCommand(Number is called
MyItem.sendCommand(two) // Success: MyItem.sendCommand(Number) is called
MyItem.sendCommand(three) // Success: MyItem.sendCommand(int) is called
MyItem.sendCommand(four) // Success: MyItem.sendCommand(String) is called
You will notice that I skipped over zero above. This is because there is what I am increasingly considering to be a bug in OH. This is beyond the scope of your question and might confuse you more but it also might help further explain. This is also only an issue with Number Items. What will happen is zero will also fail when using the MyItem.sendCommand. This goes back a few paragraphs.
Remember back when I talked about inheritance. Well, DecimalType is a child of Number. This means DecimalType can be treated as if it were a Number, to include casting it to a Number (e.g. MyItem.state as Number). You will notice above there is both a MyItem.sendCommand(DecimalType) and a MyItem.sendCommand(Number) method. So when you call MyItem.sendCommand(zero) the language gets confused. It doesn’t know whether you mean to call MyItem.sendCommand(DecimalType) or MyItem.sendCommand(Number). You will see an error in the logs that says something like “Ambiguous method call”.