Question

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
Was it helpful?

Solution

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

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top