Reputation: 148
I'm struggling to figure an efficient way to check if the user entered a valid input which is only numbers (0~9), using syscall 8
the input is 4 bytes long (1 word),
and I need to check for each byte (char) if it's a number or not,
I thought of trying to run through a loop and check if its ascii value is lower than 48 ('0' ascii decimel value),
or higher than 57('9' ascii decimel value),
Is this an efficient way of doing this?
and if so, what's the right way to implement such if statement in MIPS?
for example
if (x < 48) or (x > 57) {...}
*Note: assume I do have a flag to know where to stop the loop
*Edit: this is a segment of the code, to make it clearer:
.data
number:
.word w1
.text
.globl main
main:
la $a0, number # read the number input from user
li $a1, 6 # buffer set to max of 4 bytes (32 bits),
# assuming the input is no more than the length of a word,
# + 2 reserved bytes
li $v0, 8
syscall
Upvotes: 2
Views: 5824
Reputation: 365277
The unsigned compare trick for range checks is good here, getting the job done with only sub/sltiu/beq, and giving you the ASCII->integer digit value as part of it.
x86 example. Like for checking for alphabetic characters, see What is the idea behind ^= 32, that converts lowercase letters to upper and vice versa?
Zero-extend or sign-extend a character (a byte) into a 32-bit register; either one works because the ASCII 0..9 digit range is signed positive, and this range check correctly excludes all other 32-bit numbers.
# lbu $t0, x # for example
# ASCII character in $t0
addiu $t0, $t0, -0x30 # subtract '0' : range shift from '0'..'9' to 0..9
sltiu $t1, $t0, 10 # t1 = c < 10 unsigned
beqz $t1, non_digit # jump if c>=10
# fall through: it was a digit, 0..9 value in $t0
...
non_digit:
Input too low means t0 -= '0'
wraps to a large unsigned value. Input too high means it's (unsigned) above 9
after subtracting.
If you want to keep around the original character value as well as its integer digit value (if it's a digit), pick different registers.
MARS unfortunately doesn't support convenient ASCII character constants as part of numeric expressions, so it can't assemble addiu $t0, $t0, -'0'
to subtract '0'
.
You can write sltiu
/ beqz
as a bgeu $t0, 10, non_digit
pseudo-instruction.
You could write bgtu $t0, 9, non_digit
, but don't because MARS assembles that to 3 machine instructions (including addi
to materialize 9
in a register + sltu
between two regs), instead of 2. You can't save anything by using a register holding 9
or 10
setup before a loop, either: MIPS branch conditions only compare against zero, except for eq / ne. blt $t1, $t9, non_digit
would be a pseudo-instruction, too.
Branching only once is of course very good on a real MIPS with branch-delay slots.
You can of course use this as the loop condition for looping until you encounter a non-digit.
Upvotes: 0
Reputation: 6266
Try this:
lbu $t0, x # read next byte
sltiu $t1, $t0, 48 # t1 = (x < 48) ? 1 : 0
bnez $t1, fail
sltiu $t1, $t0, 58 # t1 = (x < 58) ? 1 : 0
beqz $t1, fail
# we now know that 48 <= $t1 <= 57
. . .
. . .
fail: # input byte was not a digit if we get to here
Upvotes: 1