ARM Thumb-2 (VFP + printf
, softfp ABI), 109 104 bytes
Uses the softfp ABI (default on Android). It won't work on armhf
Linux. f7ff fffe
is a 4 byte linker placeholder for printf
.
Both of these examples must be placed at an address that is not a multiple of 4 (but even), and must be in a W+X section because we modify an inline printf
string in place.
b5f8 2100 2234 3a01 b402 d8fc f2c4 0249
ec42 1b10 ed9f 1b11 f810 2b01 b192 3a61
bf48 3220 eb0d 02c2 ed92 5b00 bf47 ee35
5b40 ee00 0b01 ee35 5b00 ee00 0b41 ed82
5b00 e7e9 2641 bc0c b11b a005 7006 f7ff
fffe 3601 2e5a ddf6 bdf8 999a 9999 9999
3fa9 2558 322e 0066
Assembly:
.syntax unified
.arch armv6t2
// We need VFPv2 for this.
.fpu vfpv2
// We modify an inline string in place, so we need W+X.
.section ".writeable_text","awx",%progbits
.thumb
.globl silly_stocks
.thumb_func
// Must NOT be 4 byte aligned.
// .align 4
// nop
// void silly_stocks(const char *input);
// Follows AAPCS convention.
// input is a null-terminated string in r0.
silly_stocks:
push {r3-r7, lr}
// double buf[26] = {0}
movs r1, #0
movs r2, #26*2
.Lclear_loop:
subs r2, #1
push {r1}
bhi .Lclear_loop
.Lclear_loop_end:
// note: r1 and r2 are zero at this point
// r1:r2 = 0x4049000000000000 = 50.0 double
movt r2, #0x4049
vmov d0, r1, r2
vldr d1, .Lfloat_pool
.Lprocess_loop:
// Loop until the null terminator.
// while (r2 = *input++)
ldrb r2, [r0], #1
cbz r2, .Lprocess_loop_end
// tolower(r2) - 'a'
//
// Specifically, we subtract 'a', and if the char was uppercase,
// it would be negative, triggering the N flag, which is why we
// check the mi condition in the following IT blocks.
subs r2, #'a'
it mi
// if (isupper(r2))
addmi r2, #'a' - 'A'
// Yucky. We can't use proper offsetting with VFP and we need to
// keep the flags for the IT block.
add.w r2, sp, r2, lsl #3
// Behold, the beautiful syntax of VFP/NEON.
// Combine it with a an IT block and you get a 10 letter mnemonic. OwO
vldr.64 d5, [r2]
ittee mi
// if (isupper(r2)) { // Buy
vsubmi.f64 d5, d5, d0 // profit[i] -= d0
vmlami.f64 d0, d0, d1 // stock_price += stock_price * 0.05
// } else { // Sell
vaddpl.f64 d5, d5, d0 // profit[i] += stock_price
vmlspl.f64 d0, d0, d1 // stock_price -= stock_price * 0.05
// } endif
vstr.64 d5, [r2]
b .Lprocess_loop
.Lprocess_loop_end:
movs r6, #'A'
.Lprint_loop:
// Pop the double into r2 and r3. We are using the softfp abi, so
// doubles are passed in integer registers.
pop {r2, r3}
// Only check if the most signicant half of the double is zero.
// Any non-zero double that is 0x00000000xxxxxxxx is denormal.
cbz r3, .Lprint_loop_skip
// Set up an AEABI call to printf.
adr r0, .Lprintf_str
// Instead of %c, store the char directly (that's why we need W+X)
strb r6, [r0]
bl printf
.Lprint_loop_skip:
// Loop from A-Z.
adds r6, #1
cmp r6, #'Z'
ble .Lprint_loop
pop {r3-r7, pc}
// At least for the softfp ABI, doubles are smaller than floats.
// This is because printf takes doubles, not floats, and we need
// to pass them in a register pair.
// So we would need something like this, which is 12 bytes.
// vpop {d0}
// vcvt.f64.f32 d0, s0
// vmov r2, r3, d0
.align 2
.Lfloat_pool:
.double 0.05
.Lprintf_str:
// Our printf string. We modify X directly.
.asciz "X%.2f"
.section ".testsuite_unscored","ax",%progbits
.macro $TEST str
adr r0, 1f
bl puts
adr r0, 1f
bl silly_stocks
movs r0, #'\n'
bl putchar
b 2f
.align 2
1:
.asciz "\str"
.align 2
2:
.endm
.align 2
.globl main
.thumb_func
main:
push {r4, lr}
$TEST "AABaBbba"
$TEST "DGdg"
$TEST "ADJdja"
pop {r4, pc}
Decided to take a shot at VFP code in ARM assembly instead of doing something easy (and basically identical to the Java entry) like C or C++.
One of the things I realized the hard way was that ARM actually needs the stack to be 8 byte aligned, or else subtle bugs will occur. Two hours of debugging I am NEVER getting back. 😫
Oddly enough, using doubles is smaller than floats, despite the larger literal pool. This is because the C calling convention mandates that float
s be promoted to double
in variadic functions, so we would have to convert to double
anyways, and that is larger than the 8 bytes for the larger pool.
Output isn't pretty. But the question says "separators not required" so I took that in the most literal sense. 😏
AABaBbba
A7.49B2.47
DGdg
D5.12G-0.13
ADJdja
A2.24D5.38J-0.14
But aside from the 5.12
which is the same rounding error that was mentioned in Python, the results are correct.
ARM Thumb-2 (VFP + printf
, softfp ABI, very imprecise), 99 96 bytes
This version uses 32-bit x 16-bit fixed point arithmetic.
It lacks the required 6 digits of precision, but for the test cases it products the same results. Interpret it as you wish.
Must be loaded at an address that is not a multiple of 4 for constant pool alignment.
Machine code:
b5f8 2100 2534 3d01 b402 d8fc f2c0 0132
4d11 f810 2b01 b172 3a61 bf48 3220 f85d
3032 fb51 f605 bf47 1a5b 4431 440b 1b89
f84d 3032 e7ed 2641 ecbd 0b02 eeba 0bc8
ec53 2b10 b11b a004 7006 f7ff fffe 3601
2e5a ddf1 bdf8 cccc 0ccc 2558 322e 0066
Assembly:
.syntax unified
.arch armv6t2
// We need VFPv2 for this.
.fpu vfpv2
// We modify an inline string in place, so we need W+X.
.section ".writeable_text_fixed","awx",%progbits
.thumb
.globl silly_stocks_fixed
.thumb_func
// Must not be 4 byte aligned.
// .align 4
// nop
// void silly_stocks_fixed(const char *input);
// Follows AAPCS convention.
// input is a null-terminated string in r0.
silly_stocks_fixed:
push {r3-r7, lr}
// We use double the width because we need to keep alignment.
// int64_t buf[26] = {0}
movs r1, #0
movs r5, #26*2
.Lclear_loop_fixed:
subs r5, #1
push {r1}
bhi .Lclear_loop_fixed
.Lclear_loop_end_fixed:
// Load our float constants from the pool.
movt r1, #0x32 // 0x00320000 = 50.0 in 16-bit fixed point
ldr r5, .L.05.fix32 // 0x0ccccccc = 0.05 in 32-bit fixed point
.Lprocess_loop_fixed:
// Loop until the null terminator.
// while (r2 = *input++)
ldrb r2, [r0], #1
cbz r2, .Lprocess_loop_end_fixed
// tolower(r2) - 'a'
//
// Specifically, we subtract 'a', and if the char was uppercase,
// it would be negative, triggering the N flag, which is why we
// check the mi condition in the following IT blocks.
subs r2, #'a'
it mi
// if (isupper(r2))
addmi r2, #'a' - 'A'
ldr r3, [sp, r2, lsl #3]
// fixed point multiply:
// x<fixA> = (y<fixB> * z<fixC>) >> (B + C - A)
// tmp<fix16> = (stock_price<fix16> * 0.05<fix32>) >> 32
smmul r6, r1, r5 // r6 = stock_price * 0.05
ittee mi
// if (isupper(r2)) { // Buy
submi r3, r1 // profit[i] -= stock_price
addmi r1, r6 // stock_price += stock_price * 0.05
// } else { // Sell
addpl r3, r1 // profit[i] += stock_price
subpl r1, r6 // stock_price -= stock_price * 0.05
// } endif
str r3, [sp, r2, lsl #3]
b .Lprocess_loop_fixed
.Lprocess_loop_end_fixed:
movs r6, #'A'
.Lprint_loop_fixed:
// Pop the result into d0 (also cleaning the stack)
vpop {d0}
// Convert from fix16 to double
vcvt.f64.s32 d0, d0, #16
// Move the double result into r2 and r3. This is for the softfp ABI
// which passes floating point values in registers.
vmov r2, r3, d0
// Only check if the most signicant half of the double is zero.
// Any non-zero double that is 0x00000000xxxxxxxx is denormal.
cbz r3, .Lprint_loop_skip_fixed
// Set up an AEABI call to printf.
adr r0, .Lprintf_str_fixed
// Instead of %c, store the char directly (that's why we need W+X)
strb r6, [r0]
bl printf
.Lprint_loop_skip_fixed:
// Loop from A-Z.
adds r6, #1
cmp r6, #'Z'
ble .Lprint_loop_fixed
pop {r3-r7, pc}
// 0.05 in fix32.
.L.05.fix32:
.word 0x0ccccccc
// Our printf string. We replace the X when we use it.
.Lprintf_str_fixed:
.asciz "X%.2f"
.section ".testsuite_unscored","ax",%progbits
.macro $TEST str
adr r0, 1f
bl puts
adr r0, 1f
bl silly_stocks_fixed
movs r0, #'\n'
bl putchar
b 2f
.align 2
1:
.asciz "\str"
.align 2
2:
.endm
.align 2
.globl main
.thumb_func
main:
push {r4, lr}
$TEST "AABaBbba"
$TEST "DGdg"
$TEST "ADJdja"
pop {r4, pc}
For the reference, here is the results for float vs fixed point at 6 digits of precision.
AABaBbba
float: A:7.488484,B:2.474079,
fixed: A:7.488434,B:2.474060,
DGdg
float: D:5.125000,G:-0.131250,
fixed: D:5.124969,G:-0.131256,
ADJdja
float: A:2.237828,D:5.381250,J:-0.137812,
fixed: A:2.237808,D:5.381226,J:-0.137817,
round
s stuff weirdly;5.125
rounds to5.12
by default. Is this accepted? \$\endgroup\$