Yeah, well a set-oriented method is a great goal, so do not shift away from that.
But ASE is also a transaction engine, therefore additionally you have to be aware of transactions and transaction requirements and consequences. If you have 1000 rows, you have to decide on what your transaction is before you execute: is it 500 or 1000 ? "1000 if it works, and 500 if it fails" is not a transaction. The whole idea is the transaction must be Atomic (either the whole thing succeeds xor the whole thing fails). Updating 1000 rows (or 1,000,000) is not a transaction for many reasons, just one of which is exactly the problem you have described. Autocommit (Chained) is deleterious to transaction handling.
Third, each error has a Severity Level. Lower Severity Levels allow the code to continue; higher Severity Levels stop the code and the xact, automatically rollback the xact (and other Severity Levels do not) and allow error handling; highest levels entirely blow the batch  away (no error handling will catch it).
The solution looks like this. Decide on your actual, real, transaction size, let's say it is 10. This construct keeps the transactions small, and provides a method of breaking up large batches of insert/update/delete into manageable units:
SET ROWCOUNT 10
WHILE (1 = 1)
UPDATE tab_a SET
salary = salary * 1.5
WHERE salary != salary * 1.5
SET @err = @@ERROR,
@rows = @@ROWCOUNT
IF @err != 0
PRINT "Update failed with error %1!"
IF @err = 0 AND @rows = 0
SET ROWCOUNT 10
1 Of course you need to add full error checking, I have given you the basics.
2 If the update was based on a Key (rather than on non-key columns such as "salary != salary * 1.5), the code is more flexible and restartable. Eg. remove the BREAK, it will process 990 rows; that is, only the transaction with the error gets rolled back. Without a Key-based update, you cannot restart after the N+1 or N+transaction_size row.
2.1 If your transaction size is 1, then you will get 999 hits and fail on just the row that encounters an error.
3 A "batch" as per the manuals, is all code between two "go" statements.